Академический Документы
Профессиональный Документы
Культура Документы
2009
Copyright c 2009, 2008, 2007 Fbio Henrique Viduani Martinez Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled GNU Free Documentation License.
S UMRIO
0 Boas Vindas! 1 Breve Histria do Computador 1.1 1.2 Pr-histria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Sculo XX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2.1 1.2.2 Computadores Eletromecnicos . . . . . . . . . . . . . . . . . . . . . . . . Computadores Eletrnicos . . . . . . . . . . . . . . . . . . . . . . . . . . .
1 2 2 5 5 7 19 19 20 22 25 25 30 30 34 34 34 35 36 37 39 39 40
2 Como funciona um computador 2.1 2.2 2.3 Contextualizao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Arquitetura de von Neumann . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Algoritmos e programas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3 Dicas iniciais 3.1 3.2 3.3 Interface do sistema operacional . . . . . . . . . . . . . . . . . . . . . . . . . . . . Compilador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Emacs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4 Primeiros programas 4.1 4.2 4.3 4.4 4.5 Digitando . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Compilando e executando . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Olhando o primeiro programa mais de perto . . . . . . . . . . . . . . . . . . . . . Prximo programa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Documentao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6 Estruturas condicionais 6.1 6.2 6.3 Estrutura condicional simples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Estrutura condicional composta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Troca de contedos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
42 42 43 44 46 46 49 50 53 53 54 57 59 62 62 63 64 70 70 72 76 76 79 79 81 87 89 91 ii
7 Nmeros inteiros 7.1 7.2 7.3 Constantes e variveis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Expresses aritmticas com nmeros inteiros . . . . . . . . . . . . . . . . . . . . . Representao de nmeros inteiros . . . . . . . . . . . . . . . . . . . . . . . . . . .
9 Exerccios 10 Exerccios 11 Expresses com inteiros 11.1 Expresses aritmticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.2 Expresses relacionais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.3 Expresses lgicas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 Estrutura de repetio for 12.1 Estruturas de programao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.2 Estrutura de repetio for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 Estrutura de repetio do-while 13.1 Denio e exemplo de uso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 Nmeros com ponto utuante 14.1 Constantes e variveis do tipo ponto utuante . . . . . . . . . . . . . . . . . . . . 14.2 Expresses aritmticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 Exerccios 16 Exerccios 17 Caracteres
17.1 Representao grca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17.2 Constantes e variveis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17.3 Expresses com caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 Tipos de dados bsicos 18.1 Tipos inteiros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
91 93 95 97 97
18.2 Nmeros com ponto utuante . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 18.3 Caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104 18.4 Converso de tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 18.5 Tipos de dados denidos pelo programador . . . . . . . . . . . . . . . . . . . . . . 111 18.6 Operador sizeof . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 18.7 Exerccios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 19 Vetores 115
19.1 Motivao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 19.2 Denio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 19.3 Inicializao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118 19.4 Exemplo com vetores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120 19.5 Macros para constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121 20 Invariantes 124
20.1 Denio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124 20.2 Exemplos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124 21 Exerccios 22 Exerccios 23 Cadeias de caracteres 129 131 133
23.1 Literais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 23.2 Vetores de caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 23.3 Cadeias de caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 24 Matrizes 140
24.1 Denio, declarao e uso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 24.2 Declarao e inicializao simultneas . . . . . . . . . . . . . . . . . . . . . . . . . 142 24.3 Exemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143 iii
27.1 Denio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 27.2 Declarao e inicializao simultneas . . . . . . . . . . . . . . . . . . . . . . . . . 154 27.3 Operaes sobre registros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155 27.4 Exemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156 28 Vetores, matrizes e registros 159
28.1 Vetores de registros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159 28.2 Registros contendo variveis compostas . . . . . . . . . . . . . . . . . . . . . . . . 163 29 Registros com registros 167
29.1 Registros contendo registros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167 29.2 Exemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168 30 Unies e enumeraes 172
31.1 Depurador GDB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180 31.2 Primeiro contato . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181 31.3 Sintaxe dos comandos do GDB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182 31.4 Pontos de parada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183 31.5 Programa fonte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185 31.6 Vericao de dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186 31.7 Alterao de dados durante a execuo . . . . . . . . . . . . . . . . . . . . . . . . 186 31.8 Resumo dos comandos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187 31.9 Exemplos de execuo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188 32 Ecincia de programas 197
32.1 Programas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197 32.2 Anlise de algoritmos e programas . . . . . . . . . . . . . . . . . . . . . . . . . . . 198 32.2.1 Ordem de crescimento de funes matemticas . . . . . . . . . . . . . . . 201 iv
32.3 Anlise da ordenao por trocas sucessivas . . . . . . . . . . . . . . . . . . . . . . 202 32.4 Resumo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205 33 Introduo s funes 207
33.1 Noes iniciais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207 33.2 Denio e chamada de funes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212 33.3 Finalizao de programas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215 33.4 Exemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216 33.5 Declarao de funes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217 34 Exerccios 35 Argumentos e parmetros de funes 222 225
35.1 Argumentos e parmetros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225 35.2 Escopo de dados e de funes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227 36 Funes e vetores 231
36.1 Vetores como argumentos de funes . . . . . . . . . . . . . . . . . . . . . . . . . . 231 36.2 Vetores so parmetros passados por referncia . . . . . . . . . . . . . . . . . . . . 232 36.3 Vetores como parmetros com dimenses omitidas . . . . . . . . . . . . . . . . . . 233 37 Funes e matrizes 236
37.1 Matrizes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236 37.2 Matrizes como parmetros com uma dimenso omitida . . . . . . . . . . . . . . . 237 38 Funes e registros 241
38.1 Tipo registro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241 38.2 Registros e passagem por cpia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243 38.3 Registros e passagem por referncia . . . . . . . . . . . . . . . . . . . . . . . . . . 245 38.4 Funes que devolvem registros . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247 39 Recurso 250
39.1 Denio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250 39.2 Exemplos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251 40 Exerccios 41 Busca v 255 258
41.1 Busca seqencial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258 41.2 Busca em um vetor ordenado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259 42 Ordenao: mtodos elementares 266
42.1 Problema da ordenao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 266 42.2 Mtodo das trocas sucessivas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 266 42.3 Mtodo da seleo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267 42.4 Mtodo da insero . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268 43 Ordenao por intercalao 270
43.1 Dividir para conquistar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270 43.2 Problema da intercalao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270 43.3 Ordenao por intercalao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272 44 Ordenao por separao 275
44.1 Problema da separao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275 44.2 Ordenao por separao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277 45 Biblioteca padro 281
45.1 Qualicadores de tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 281 45.2 Arquivo-cabealho . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282 45.3 Arquivos-cabealhos da biblioteca padro . . . . . . . . . . . . . . . . . . . . . . . 286 45.3.1 Diagnsticos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286 45.3.2 Manipulao, teste e converso de caracteres . . . . . . . . . . . . . . . . . 286 45.3.3 Erros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287 45.3.4 Caractersticas dos tipos com ponto utuante . . . . . . . . . . . . . . . . . 288 45.3.5 Localizao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 288 45.3.6 Matemtica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289 45.3.7 Saltos no-locais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291 45.3.8 Manipulao de sinais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292 45.3.9 Nmero varivel de argumentos . . . . . . . . . . . . . . . . . . . . . . . . 292 45.3.10 Denies comuns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292 45.3.11 Entrada e sada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292 45.3.12 Utilitrios gerais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293 45.3.13 Manipulao de cadeias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295 45.3.14 Data e hora . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 297 vi
46 Pr-processador
299
46.1 Funcionamento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299 46.2 Diretivas de pr-processamento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 300 46.3 Denies de macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301 46.4 Incluso de arquivos-cabealhos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302 46.5 Compilao condicional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304 46.6 Outras diretivas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 306 47 Programas extensos 308
47.1 Arquivos-fontes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 308 47.2 Arquivos-cabealhos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309 47.3 Diviso de programas em arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . . 313 48 Operaes sobre bits 316
48.1 Operadores bit a bit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316 48.2 Trechos de bits em registros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 321 49 Introduo aos apontadores 324
49.1 Variveis apontadoras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 324 49.2 Operadores de endereamento e de indireo . . . . . . . . . . . . . . . . . . . . . 326 49.3 Apontadores em expresses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329 50 Apontadores e funes 332
50.1 Parmetros de entrada e sada? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332 50.2 Devoluo de apontadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335 51 Apontadores e vetores 338
51.1 Aritmtica com apontadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338 51.2 Uso de apontadores para processamento de vetores . . . . . . . . . . . . . . . . . 342 51.3 Uso do identicador de um vetor como apontador . . . . . . . . . . . . . . . . . . 343 52 Apontadores e matrizes 349
52.1 Apontadores para elementos de uma matriz . . . . . . . . . . . . . . . . . . . . . 349 52.2 Processamento das linhas de uma matriz . . . . . . . . . . . . . . . . . . . . . . . 350 52.3 Processamento das colunas de uma matriz . . . . . . . . . . . . . . . . . . . . . . 351 52.4 Identicadores de matrizes como apontadores . . . . . . . . . . . . . . . . . . . . 352
vii
354
53.1 Literais e apontadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354 53.2 Vetores de cadeias de caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 358 53.3 Argumentos na linha de comandos . . . . . . . . . . . . . . . . . . . . . . . . . . . 360 54 Apontadores e registros 366
54.1 Apontadores para registros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 366 54.2 Registros contendo apontadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368 55 Uso avanado de apontadores 371
55.1 Alocao dinmica de memria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371 55.2 Apontadores para apontadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 376 55.3 Apontadores para funes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 377 56 Arquivos 382
56.1 Seqncias de caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 382 56.2 Redirecionamento de entrada e sada . . . . . . . . . . . . . . . . . . . . . . . . . . 383 56.3 Funes de entrada e sada da linguagem C . . . . . . . . . . . . . . . . . . . . . . 384 56.3.1 Funes de abertura e fechamento . . . . . . . . . . . . . . . . . . . . . . . 385 56.3.2 Funes de entrada e sada . . . . . . . . . . . . . . . . . . . . . . . . . . . 386 56.3.3 Funes de controle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 388 56.3.4 Funes sobre arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 390 56.3.5 Arquivos do sistema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 391 56.4 Exemplos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392 57 Listas lineares 396
57.1 Denio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 396 57.2 Busca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 399 57.3 Insero . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 400 57.4 Remoo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401 58 Pilhas 406
58.1 Denio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 406 58.2 Operaes bsicas em alocao seqencial . . . . . . . . . . . . . . . . . . . . . . . 406 58.3 Operaes bsicas em alocao encadeada . . . . . . . . . . . . . . . . . . . . . . . 409
viii
59 Filas
412
59.1 Denio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412 59.2 Operaes bsicas em alocao seqencial . . . . . . . . . . . . . . . . . . . . . . . 413 59.3 Operaes bsicas em alocao encadeada . . . . . . . . . . . . . . . . . . . . . . . 415 60 Listas lineares circulares 419
60.1 Alocao encadeada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 419 60.2 Busca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420 60.3 Insero . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420 60.4 Remoo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 421 61 Listas lineares duplamente encadeadas 424
61.1 Denio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 424 61.2 Busca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 425 61.3 Insero . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426 61.4 Remoo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426
ix
A ULA 0
B OAS V INDAS !
Esta aula reservada para ambientar o(a) estudante no laboratrio, fazendo-o(a) logar em um computador, experimentar o ambiente de trabalho e a ferramenta de suporte a disciplinas da faculdade (SSD-FACOM), onde dever cadastrar seu perl de usurio(a) e se cadastrar nas disciplinas de seu semestre. Essa ser a ferramenta de interface entre o(a) professor(a) e os(as) estudantes durante todo o curso. Alm disso, as informaes gerais sobre a disciplina, tais como a forma de avaliao, datas de provas e trabalhos, critrios de avaliao e mdia nal, alm da conduta tica esperada dos(as) estudantes, so explicitadas nesse ambiente.
A ULA 1
B REVE H ISTRIA
DO
C OMPUTADOR
Nesta aula abordaremos uma pequena histria do computador, desde seu surgimento at os nossos dias. Sabemos, no entanto, que esta na verdade uma tentativa e que fatos histricos relevantes podem ter sido excludos involuntariamente. Continuaremos a trabalhar no sentido de produzir um texto cada vez mais amplo e completo, buscando informaes em outras fontes ainda desconhecidas. Este texto baseado principalmente nas pginas1 da Rede Mundial de Computadores [2, 3, 9, 13, 18] e nos livros de Philippe Breton [1] e Russel Shackelford [14].
1.1 Pr-histria
Quando comeou a realizar tarefas mecnicas para sua comodidade e bem estar, o ser humano predominantemente as mulheres tinha de realizar, hora aps hora, dia aps dia, todos os clculos repetitivos em tarefas das cincias, do comrcio e da indstria. Tabelas e mais tabelas eram preenchidas com os resultados desses clculos, para que no fossem sempre refeitos. No entanto, erros nesses clculos eram freqentes devido, em especial, ao tdio e desconcentrao. Alm disso, esses clculos no eram realizados de forma rpida. Assim, muitos inventores tentaram por centenas de anos construir mquinas que nos ajudassem a realizar essas tarefas. O baco2 provavelmente o mais antigo instrumento conhecido de auxlio ao ser humano em clculos matemticos. O baco mais antigo foi descoberto na Babilnia, hoje conhecida como Iraque, e data do ano de 300 A.C. Seu valor est especialmente no fato de auxiliar a memria humana durante uma operao aritmtica. Algum bem treinado no uso de um baco pode executar adies e subtraes com a mesma velocidade de quem usa uma calculadora eletrnica, mas outras operaes bsicas so mais lentas. Esse instrumento ainda consideravelmente utilizado em pases do Oriente.
Essas pginas so muito legais! Vale a pena visit-las! Abacus uma palavra em latim que tem sua origem nas palavras gregas abax ou abakon, que quer dizer tabela e que, por sua vez, tem sua origem possivelmente na palavra semtica abq, que signica areia (do livro The Universal History of Numbers de Georges Ifrah, Wiley Press 2000).
1.1 P R - HISTRIA
John Napier3 , em seu trabalho Mirici Logarithmorum Canonis Descriptio de 1614, inventou o logaritmo, uma ferramenta que permitia que multiplicaes fossem realizadas via adies e divises via subtraes. A mgica consistia na consulta do logaritmo de cada operando, obtido de uma tabela impressa. A inveno de John Napier deu origem rgua de clculo, instrumento de preciso construdo inicialmente na Inglaterra em 1622 por William Oughtred4 e utilizado nos projetos Mercury, Gemini e Apollo da NASA, que levaram o homem lua.
Figura 1.2: Rgua de clculo. Leonardo da Vinci5 e Wilhelm Schickard6 , nos sculos XVI e XVII, respectivamente, foram os primeiros a projetar mquinas de clculo baseadas em engrenagens. A mquina proposta por da Vinci nunca saiu do papel, mas a mquina de Schickard conhecida como a primeira calculadora mecnica automtica ou o primeiro computador no programvel. O sucesso de uma mquina como essa s foi obtido por Blaise Pascal7 em 1642, que aos 19 anos construiu a Pascaline para ajudar seu pai, um recolhedor de taxas, a somar quantias. A Pascaline realizava apenas adies e no era muito precisa. Pouco tempo aps Pascal ter proposto sua mquina de calcular, o alemo Gottfried Leibniz8 , co-inventor do Clculo juntamente com Isaac Newton9 , planejou construir uma calculadora10 com as quatro operaes bsicas e que, ao invs de engrenagens, empregava cilindros dentados com 10 dentes, possibilitando seu trabalho no sistema numrico decimal. O sculo seguinte tem poucas inovaes diretamente relacionadas aos computadores, como a escala de temperaturas Fahrenheit11 , o telgrafo e a eletricidade, descoberta por Benjamin Franklin12 . Joseph Jacquard13 criou, em 1801, um tear que podia tecer a partir de um padro lido automaticamente de cartes de madeira perfurados e conectados por cordes. Descendentes desses cartes perfurados so utilizados at hoje em algumas aplicaes. A inveno de Jacquard incrementou a produtividade de tecidos da poca, mas, em contrapartida, gerou grande deJohn Napier ( ), nascido na Esccia, matemtico, fsico, astrnomo e 8o Lorde de Merchistoun. William Oughtred ( ), nascido na Inglaterra, matemtico. 5 Leonardo di ser Piero da Vinci ( ), nascido na Itlia, erudito, arquiteto, anatomista, escultor, engenheiro, inventor, matemtico, msico, cientista e pintor. 6 Wilhelm Schickard ( ), nascido na Alemanha, erudito. 7 Blaise Pascal ( ), nascido na Frana, matemtico, fsico e lsofo. 8 Gottfried Wilhelm Leibniz ( ), nascido na Alemanha, erudito. 9 Sir Isaac Newton ( ), nascido na Inglaterra, fsico, matemtico, astrnomo e lsofo. 10 Ele chamou sua mquina de stepped reckoner. 11 Daniel Gabriel Fahrenheit ( ), nascido na Alemanha, fsico e engenheiro. 12 Benjamin Franklin ( ), nascido nos Estados Unidos, jornalista, editor, autor, lantropo, abolicionista, funcionrio pblico, cientista, diplomata e inventor. Foi tambm um dos lderes da Revoluo Norte-americana. 13 Joseph Marie Jacquard ( ), nascido na Frana, alfaiate e inventor.
4 3
DCT
UFMS
Figura 1.3: Cartes perfurados da mquina de Jacquard. A Mquina de Diferenas 14 , projetada por Charles Babbage15 , era uma mquina de calcular a vapor e foi criada com o objetivo de atender estratgia expansionista do governo ingls, que tinha a ambio de se tornar o maior imprio do planeta. Esse foi o projeto mais caro nanciado pelo governo da Inglaterra at ento, mas aps dez anos de tentativas infrutferas, o projeto ruiu e a mquina no foi terminada. Todavia, Babbage no desistiu e projetou uma segunda mquina chamada Mquina Analtica 16 que seria alimentada por seis mquinas a vapor, teria o tamanho de uma casa e era uma mquina de propsito mais geral, j que seria programvel por meio de cartes perfurados. Alm disso, sua inveno tambm permitiria que os cartes perfurados, agora de papel, fossem empregados como um dispositivo de armazenamento. Essa mquina teria ainda um dispositivo que a distinguia das calculadoras e a aproximava do que conhecemos hoje como computadores: uma sentena condicional. Ada Byron17 tornou-se amiga de Charles Babbage e cou fascinada com o as idias da Mquina Analtica. Apesar de nunca ter sido construda, Ada escreveu diversos programas para essa mquina e entrou para a histria como a primeira programadora de um computador. Ada inventou a sub-rotina e reconheceu a importncia do lao como uma estrutura de programao. Em 1890 o governo norte-americano tinha de realizar o censo da populao dos Estados Unidos e a previso era de que, da forma como tinha sido realizado o ltimo, o censo de 1890 levaria cerca de 7 anos e meio at ser nalizado. Um prmio foi ento anunciado para o inventor que ajudasse a automatizar o censo de 1890. O prmio foi vencido por Herman Hollerith18 , que props a Mesa de Hollerith19 , uma mquina que consistia de um leitor de cartes perfurados que era sensvel aos buracos nos cartes, um mecanismo de engrenagens que podia contar e um grande expositor de indicadores para mostrar os resultados da computao. Essa mquina foi projetada com sucesso e o censo de 1890 terminou em somente 3 anos. A partir da Hollerith fundou uma empresa chamada TABULATING M ACHINE C OMPANY. Em 1911 sua empresa se une a outras duas e se torna a C OMPUTING TABULATING R ECORDING C ORPORATION CTR. Sob a presidncia de Thomas Watson20 , a empresa foi renomeada I NTERNATIONAL B USINESS M ACHINES IBM em 1924.
Do ingls Difference Engine. Charles Babbage ( ), nascido na Inglaterra, matemtico, lsofo, engenheiro mecnico e precursor da Cincia da Computao. 16 Do ingls Analytic Engine. 17 Augusta Ada Byron ( ), nascida na Inglaterra, conhecida como a primeira programadora da histria. Depois de seu casamento passou a se chamar Augusta Ada King, Condessa de Lovelace. 18 Herman Hollerith ( ), nascido nos Estados Unidos, estatstico e empresrio. 19 Do ingls Hollerith desk. 20 Thomas John Watson ( ), nascido nos Estados Unidos, empresrio.
15 14
DCT
UFMS
1.2 S CULO XX
1.2 Sculo XX
A exploso dos computadores ocorreu no sculo XX. At a metade desse sculo, computadores eletromecnicos e os primeiros computadores totalmente eletrnicos foram projetados com ns militares, para realizar clculos balsticos e decifrar cdigos dos inimigos. Eminentes cientistas, que deram origem a quase tudo do que chamamos de Cincia da Computao, estiveram envolvidos nesses projetos. A partir da segunda metade do sculo, a exploso dos computadores eletrnicos se deu, quando o computador pessoal passou a fazer parte de nosso dia a dia.
DCT
1.2 S CULO XX
comprimento e funcionava atravs de um motor de 5 cavalos-vapor. O Mark I podia operar nmeros de at 23 dgitos. Podia adicionar ou subtrair esses nmeros em 3/10 de segundo, multiplic-los em 4 segundos e dividi-los em 10 segundos22. Apesar de ser um computador enorme, com aproximadamente 750 mil componentes, o Mark I podia armazenar apenas 72 nmeros e sua velocidade de armazenamento e recuperao era muito lenta, uma motivao e um fator preponderante para substituio posterior do computador eletromecnico pelo computador eletrnico. Apesar disso, o Mark I funcionou sem parar por quinze anos.
Figura 1.5: O computador eletromecnico Harvard Mark I. Uma das primeiras pessoas a programar o Mark I foi uma mulher, Grace Hopper23, que em 1953 inventou a primeira linguagem de programao de alto nvel chamada Flow-matic, vindo a se tornar posteriormente a linguagem COBOL. Uma linguagem de programao de alto nvel projetada com o objetivo de ser mais compreensvel ao ser humano, diferentemente da linguagem binria, de baixo nvel, compreendida pelo computador. No entanto, uma linguagem de alto nvel necessita de um programa chamado compilador que traduz um programa em linguagem de alto nvel para um programa em linguagem de baixo nvel, de modo que o Mark I pudesse execut-lo. Portanto, Grace Hopper tambm construiu o primeiro compilador conhecido.
espantoso observar que aps 45 anos da fabricao do Mark I, um computador podia realizar uma operao de adio em 1/109 de segundo. 23 Grace Murray Hopper ( ), nascida nos Estados Unidos, cientista da computao e ocial da Marinha dos Estados Unidos.
22
DCT
UFMS
1.2 S CULO XX
Alan Mathison Turing ( ), nascido na Inglaterra, matemtico, lgico e criptoanalista. Esta armao chamada Tese de Church-Turing. Uma linguagem de programao ou um computador abstrato Turing-completo se satisfaz essa tese. 26 John Vincent Atanasoff ( ), nascido nos Estados Unidos, fsico. 27 Clifford Edward Berry ( ), nascido nos Estados Unidos, engenheiro eltrico. 28 Thomas Harold Flowers ( ), nascido na Inglaterra, engenheiro.
DCT
UFMS
O ttulo de primeiro computador digital de propsito geral e totalmente eletrnico em geral dado ao ENIAC (Electronic Numerical Integrator and Calculator). Esse computador foi construdo na Universidade da Pensilvnia entre 1943 e 1945 pelos professores John Mauchly29 e John Eckert30 obtendo nanciamento do departamento de guerra com a promessa de construir uma mquina que substituiria todos os computadores existentes, em particular as mulheres que calculavam as tabelas balsticas para as armas da artilharia pesada do exrcito. O ENIAC ocupava uma sala de 6 por 12 metros, pesava 30 toneladas e usava mais de 18 mil tubos a vcuo, que eram muito pouco conveis e aqueciam demasiadamente.
Figura 1.7: O primeiro computador eletrnico ENIAC. Apesar de suas 18 mil vlvulas, o ENIAC podia armazenar apenas 20 nmeros por vez. No entanto, graas eliminao de engrenagens, era muito mais rpido que o Mark I. Por exemplo, enquanto uma multiplicao no Mark I levava 6 segundos, no ENIAC levava 2,8 milsimos de segundo. A velocidade do relgio interno do ENIAC era de 100 mil ciclos por segundo31. Financiado pelo exrcito dos Estados Unidos, o ENIAC tinha como principal tarefa vericar a possibilidade da construo da bomba de hidrognio. Aps processar um programa armazenado em meio milho de cartes perfurados por seis semanas, o ENIAC infelizmente respondeu que a bomba de hidrognio era vivel.
John William Mauchly ( ), nascido nos Estados Unidos, fsico e pioneiro da computao. John Adam Presper Eckert Jr. ( ), nascido nos Estados Unidos, fsico e pioneiro da computao. 31 Diramos que o ENIAC era um computador com velocidade de 100 KHz.
30 29
DCT
UFMS
1.2 S CULO XX
O ENIAC mostrou-se muito til e vivel economicamente, mas tinha como um de seus principais defeitos a diculdade de reprogramao. Isto , um programa para o ENIAC estava intrinsecamente relacionado a sua parte fsica, em especial a os e interruptores. O EDVAC (Electronic Discrete Variable Automatic Computer) foi projetado em 1946 pela equipe de John Mauchly e John Eckert, que agregou o matemtico John von Neumann32 e tinha como principais caractersticas a possibilidade de armazenar um programa em sua memria e de ser um computador baseado no sistema binrio. John von Neumann publicou um trabalho33 descrevendo uma arquitetura de computadores em que os dados e o programa so mapeados no mesmo espao de endereos. Essa arquitetura, conhecida como arquitetura de von Neumann ainda utilizada nos processadores atuais, como por exemplo no Pentium IV.
Figura 1.8: O EDVAC. Nos anos 50, os computadores eram nada populares e pertenciam a algumas poucas universidades e ao governo norte-americano. No incio da dcada, John Eckert e John Mauchly deixam a Universidade de Pensilvnia por problemas de patenteamento de suas invenes e
32 Margittai Neumann Jnos Lajos ( ), nascido na ustria-Hungria, matemtico e erudito. John von Neumann tem muitas contribuies em diversas reas. Na Cincia da Computao, alm da arquitetura de von Neumann, props os autmatos celulares. Mas, infelizmente, tambm foi um dos cientistas a trabalhar no Projeto Manhatan, responsvel pelo desenvolvimento de armas nucleares, tendo sido responsvel pela escolha dos alvos de Hiroshima e Nagazaki no Japo na Segunda Guerra Mundial, onde explodiriam as primeiras bombas nucleares da histria e pelo clculo da melhor altura de exploso para que uma maior destruio fosse obtida. 33 First Draft of a Report on the EDV AC.
DCT
UFMS
1.2 S CULO XX
10
fundam sua prpria empresa de computadores, a E CKERT-M AUCHLY C OMPUTER C ORPORA TION. Em 1951 projetam o UNIVAC, de UNIVersal Automatic Computer, o primeiro computador eletrnico comercial produzido em larga escala e que utilizava ta magntica. O primeiro UNIVAC foi vendido para o escritrio do censo populacional dos Estados Unidos. Mas devido ao domnio da IBM, que em 1955 vendia mais computadores que o UNIVAC, a empresa de John Eckert e John Mauchly passa por muitas diculdades at ser fechada e vendida. Em 1955 a B ELL L ABORATORIES, uma empresa de tecnologia fundada em 1925, produz o primeiro computador base de transistores. Os transistores eram menores, mais rpidos e aqueciam muito menos que as vlvulas, o que tornava computadores base de transistores muito mais ecientes e conveis. Em 1957 a IBM anuncia que no usaria mais vlvulas e produz seu primeiro computador contendo 2.000 transistores. Em 1958, descobertas experimentais que mostravam que dispositivos semicondutores podiam substituir as vlvulas e a possibilidade de produzir tais dispositivo em Figura 1.9: Um circuito integrado. larga escala, possibilitaram o surgimento do primeiro circuito integrado, ou microchip, desenvolvido simultaneamente por Jack Kilby34 da T EXAS I NSTRUMENTS e por Robert Noyce35 da FAIRCHILD S EMICONDUCTOR. Um circuito integrado um circuito eletrnico miniaturizado, consistindo basicamente de dispositivos semicondutores, produzido na superfcie de um no substrato de material semicondutor. De 1960 a 1980 Nos anos 60, as empresas produziam computadores de grande porte ou mainframes. Esses computadores eram usados por grandes organizaes para aplicaes massivas, tipicamente para processamento de enormes quantidades de dados tais como censo populacional, estatsticas industriais ou comerciais e processamento de transaes nanceiras. Faz parte desse conjunto a srie IBM 1400 da IBM e a srie UNIVAC 1100 da S PERRY-R AND. Naqueles anos, juntamente com a produo de computadores, dispositivos, perifricos e linguagens de programao foram tambm deFigura 1.10: O primeiro mouse. senvolvidos, alm de propostas de padronizao desses novos recursos. Em 1963, Douglas Engelbart36 inventou e patenteou o primeiro mouse de computador. No mesmo ano desenvolvido o cdigo padro norte-americano para troca de informaes37 para padronizar a troca de dados entre computadores. Em 1967, a IBM cria o primeiro disco exvel38 para armazenamento de daJack St. Clair Kilby ( ), nascido nos Estados Unidos, engenheiro eltrico. Ganhador do Prmio Nobel de Fsica em 2000 por sua contribuio na inveno do circuito integrado. 35 Robert Noyce ( ), nascido nos Estados Unidos, fsico. Tambm conhecido como o Mestre do Vale do Silcio. 36 Douglas C. Engelbart (), nascido nos Estados Unidos, engenheiro eltrico e inventor. 37 Do ingls American Standard Code for Information Interchange ASCII. 38 Disquete ou disco exvel, do ingls oppy disk.
DCT UFMS
34
1.2 S CULO XX
11
dos. Alm disso, as linguagens de programao BASIC, FORTRAN e COBOL foram propostas nessa dcada. Com o desenvolvimento da indstria de computadores fervilhando, Gordon Moore39 , atravs de observaes empricas, arma em 1965 que o nmero de transistores em um circuito integrado duplicaria a cada 24 meses. Essa armao ca conhecida depois como Lei de Moore. Em 1968, Gordon Moore e Robert Noyce fundam a I NTEL C ORPORATION, uma empresa fabricante de processadores. Em 1969, um grupo de ex-executivos da FAIRCHILD S EMICONDUCTOR funda uma empresa de circuitos integrados chamada A DVANCED M ICRO D EVICES , I NC ., AMD. Em 1969, a AT&T B ELL L ABORATORIES desenvolve o Unix, um excelente sistema operacional utilizado ainda hoje especialmente em servidores. Entre 1969 e 1970, a primeira impressora matricial e a primeira impressora laser so produzidas.
Figura 1.11: Mquinas perfuradoras de cartes IBM 26. Como j comentamos, os computadores dos anos 60 e 70 eram computadores de grande porte estabelecidos em rgos governamentais, universidades e algumas grandes empresas. Esses computadores podiam ser programados de duas maneiras distintas. A primeira maneira conhecida como compartilhamento de tempo40 onde o computador d a cada usurio uma fatia de tempo de processamento, comportando, naquela poca, 100 usurios ativos simultaneamente. A segunda maneira chamada de modo de processamento em lote41 onde o computador d ateno total a um programa, mas no h usurios ativos. O programa, no momento em que o computador se dispe a process-lo, lido a partir de cartes perfurados que foram previamente preparados em uma mquina perfuradora de cartes. A dcada de 70 tambm foi muito prolca no desenvolvimento da computao. LinguaGordon Earle Moore (), nascido nos Estados Unidos, qumico e fsico. Do ingls time sharing processing. 41 Do ingls batch processing.
40 39
DCT
UFMS
1.2 S CULO XX
12
gens de programao usadas at hoje foram projetadas nessa poca como Pascal, concebida em 1971 por Niklaus Wirth42 , e C, proposta em 1972 por Dennis Ritchie43 . Avanos no projeto de dispositivos tambm foram obtidos. Datam dessa poca o CD44 , criado em 1972, o primeiro leitor de discos exveis, introduzido pela T OSHIBA em 1974, e o primeiro disco exvel de 5 1/4 produzido em 1978. Ainda, o ano de 1975 marca o nascimento de uma das maiores e mais bem sucedidas empresas do mundo, a M ICROSOFT, criada por Bill Gates45 e Paul Allen46 . No mesmo ano, Steve Wozniak47 e Steve Jobs48 fundam a A PPLE C OMPUTERS, tambm uma das maiores empresas fabricantes de computadores do mundo. Mas nenhum dos avanos dessa poca foi to signicativo como aquele obtido pela fabricante de microprocessadores I NTEL, que conseguiu colocar um computador inteiro em uma nica pastilha de silcio logo no incio da dcada. Em 1970 a I N TEL colocou em produo o microprocessador INTEL 4004, com barramento de 4 bits, velocidade de 108 KHz e 2.300 transistores. A encomenda inicial partiu da empresa japonesa B USICOM, que tinha a inteno de utilizar o INTEL 4004 em uma linha de calculadoras cientcas. Porm, o processador mais notvel que foi disponibilizado em seguida pela I NTEL foi o INTEL 8080 em 1974, com barramento de 8 bits e velocidade de processamento de 2 MHz. A I NTEL vendia esse processador por 360 dlares como uma brincadeira com a IBM, j que seu computador de grande porte IBM S/360 custava milhes de dlares. Em 1975, o INTEL 8080 foi empregado no Altair 8800 da M ICRO I NS TRUMENTATION AND T ELEMETRY S YSTEMS, considerado o primeiro computador pessoal49 , tinha 1 Kbyte de memria principal e tinha de ser construdo a partir de um kit de peas que chegavam pelo correio. Bill Gates, que acabara de ingressar na Universidade de Harvard, largou seus estudos para concentrar esforos na escrita de programas para esse computador.
Em 1976, trs computadores pessoais concorrentes so colocados venda. O primeiro computador pessoal que j vinha montado pela fbrica, o Apple I, lanado pela A PPLE C OMPU TERS. Logo em seguida, em 1977, a A PPLE inicia a produo do Apple II, o primeiro computador pessoal com monitor colorido, projetado com o processador 6502 da MOS T ECHNOLOGY, de 8 bits de barramento, 1 MHz de velocidade, 4 Mbytes de memria principal e interface de
Niklaus E. Wirth (), nascido na Sua, engenheiro eletrnico e cientista da computao. Dennis MacAlistair Ritchie (), nascido nos Estados Unidos, fsico e matemtico. 44 Do ingls compact disk. 45 William Henry Gates III (), nascido nos Estados Unidos, empresrio. 46 Paul Gardner Allen (), nascido nos Estados Unidos, empresrio. 47 Stephan Gary Wozniak (), nascido nos Estados Unidos, engenheiro da computao e lantropo. 48 Steven Paul Jobs (), nascido nos Estados Unidos, empresrio. 49 Do ingls personal computer PC.
43 42
DCT
UFMS
1.2 S CULO XX
13
udio-cassete para armazenamento e recuperao de dados em ta cassete. Era o computador padro empregado na rede de ensino dos Estados Unidos nos anos 80 e 90.
Figura 1.13: O computador pessoal Apple I. Alm do Apple I, um outro computador pessoal que entrou no mercado de computadores pessoais a partir de 1976 foi o TRS-80 Model I da TANDY C ORPORATION. Esse computador continha um microprocessador de 8 bits, o Zilog Z80, de 1,77 MHz de velocidade e tinha memria de 4 Kbytes. Tambm em 1976, a I NTEL lana o processador INTEL 8086, ainda conhecido como P1, com barramento de 16 bits, velocidade de 5 MHz e 29.000 transistores, que d origem arquitetura de processadores x86. O INTEL 8088 lanado em 1979 baseado no INTEL 8086, mas tem barramento de dados de 8 bits, permitindo compatibilidade com os processadores anteriores. Esse processador foi utilizado como o processador padro da linha de computadores pessoais da IBM, os IBM PCs, a partir de 1981. J em 1980, a IBM contrata Bill Gates e Paul Allen para desenvolver um sistema operacional para o IBM PC, que eles denominaram Disk Operating System DOS. O DOS, ou MS-DOS, era um sistema operacional com interface de linha de comandos. De 1980 a 2000 Com o mercado de computadores em contnuo crescimento, os anos 80 tambm foram de muitas inovaes tecnolgicas na rea. O crescimento do nmero de computadores pessoais nos Estados Unidos nessa poca revela o signicado da palavra exploso dos computadores. Para se ter uma idia, em 1983 o nmero de computadores pessoais em uso nos Estados Unidos era de 10 milhes, subindo para 30 milhes em 1986 e chegando a mais de 45 milhes em 1988. Logo em 1982, a A PPLE C OMPUTERS a primeira empresa fabricante de computadores pessoais a atingir a marca de 1 bilho de dlares em vendas anuais.
DCT
UFMS
1.2 S CULO XX
14
Figura 1.14: O computador pessoal IBM PC. Processadores, e conseqentemente os computadores pessoais, foram atualizados a passos largos. A E PSON C ORPORATE H EADQUARTERS, em 1982, introduz no mercado o primeiro computador pessoal porttil50 . Os processadores da famlia x86 286, 386 e 486 foram lanados gradativamente pela I NTEL na dcada de 80 e foram incorporados em computadores pessoais de diversas marcas. O INTEL 80486, por exemplo, era um processador de 32 bits, 50 MHz e 1,2 milhes de transistores. Processadores compatveis com a famlia x86 da I NTEL foram produzidos por outras empresas como IBM, T EXAS I NSTRUMENTS, AMD, C YRIX e C HIPS AND T ECHNOLOGIES. Em 1984, a A PPLE lana com sucesso estrondoso o primeiro MacIntosh, o primeiro computador pessoal a usar uma interface grca para interao entre o usurio e a mquina, conhecido como MacOS. O MacIntosh era equipado com o processador 68000 da M OTOROLA de 8 MHz e 128 Kbytes de memria. Uma atualizao com expanso de memria de 1 Mbytes foi feita em 1986 no MacIntosh Plus. Os programas tambm evoluram na dcada de 80. O sistema operacional MS-DOS partiu gradativamente da verso 1.0 em 1981 e atingiu a verso 4.01 em 1988. Em 1985, o sistema operacional Windows 1.0 vendido por 100 dlares pela M ICROSOFT em resposta tendncia crescente de uso das interfaces grcas de usurios popularizadas pelo MacIntosh. Em 1987 o Windows 2.0 disponibilizado. Nessa poca a A PPLE trava uma batalha judicial por cpia de direitos autorais do sistema operacional do MacIntosh contra a M ICROSOFT, pelo sistema operacional Windows 2.03 de 1988, e a H EWLETT-PACKARD, pelo sistema operacional NewWave de 1989. Em 1990 Tim Berners-Lee51 prope um sistema de hipertexto que o primeiro impulso da Rede Mundial de Computadores52 . O primeiro provedor comercial de linha discada da Internet torna-se ativo em 1991, quando a WWW disponibilizada para o pblico em geral como uma ferramenta de busca.
50
Tambm conhecido como notebook. Sir Timothy John Berners-Lee (), nascido na Inglaterra, fsico. 52 Do ingls World Wide Web WWW.
51
DCT
UFMS
1.2 S CULO XX
15
Richard Stallman53 , em 1985, escreve o Manifesto GNU que apresenta sua motivao para desenvolver o sistema operacional GNU, o primeiro projeto de software livre proposto. Desde meados dos anos 90, Stallman tem gastado muito de seu tempo como um ativista poltico defendendo o software livre, bem como fazendo campanha contra patentes de software e a expanso das leis de direitos autorais. Os mais destacados programas desenvolvidos por Stallman so o GNU Emacs, o GNU Compiler Collection (gcc) e o GNU Debugger (gdb). Inspirado pelo Minix, um sistema operacional baseado no Unix voltado para o ensino, Linus Torvalds54 projetou em 1991 um sistema operacional para computadores pessoais chamado Linux. O Linux um exemplo de destaque do que chamamos de software livre e Figura 1.15: O smbolo do Linux. de desenvolvimento de cdigo aberto. Seu cdigo fonte, escrito na linguagem C, disponibilizado para qualquer pessoa usar, modicar e redistribuir livremente. O sistema operacional MS-DOS teve seu m na dcada de 90, em sua ltima verso comercial 6.22, tendo sido completamente substitudo pelo bem sucedido Windows. A verso 3.0 do Windows vendeu 3 milhes de cpias em 1990. Em 1992, a verso 3.1 vendeu 1 milho de cpias em apenas 2 meses depois de seu lanamento. J em 1997, aps o lanamento do Windows 95 em 1995, Bill Gates reconhecido como o homem mais rico do mundo. Nessa poca de exploso do uso dos sistemas operacionais da M ICROSOFT, os vrus de computador passaram a infestar cada vez mais computadores e a impor perdas cada vez maiores de tempo, de recursos e de dinheiro s pessoas e empresas que utilizavam tais sistemas operacionais. Um dos primeiros e mais famosos vrus de computador o Monkey Virus, um vrus de setor de boot descoberto no Canad em 1991, que se espalhou muito rapidamente pelos Estados Unidos, Inglaterra e Austrlia. Seguiu-se ao Windows 95 o Windows 98 em 1998 e o Windows ME em 2000. A Rede Mundial de Computadores e a Internet tambm mostram um desenvolvimento destacado nessa poca. O ano de 1993 registra um crescimento espantoso da Internet e 50 servidores WWW j so conhecidos at aquele momento. Em 1994, os estudantes de doutorado em engenharia eltrica da Universidade de Stanford Jerry Yang55 e David Filo56 fundam a Yahoo!, uma empresa de servios de Internet que engloba um portal de Internet, uma ferramenta de busca na Internet, servio de e-mail, entre outros. A Yahoo! obtm grande sucesso entre usurios e em outubro de 2005 sua rede de servios espalhada pelo mundo recebe em mdia 3,4 bilhes de visitas por dia. Em 1994, o World Wide Web Consortium W3C fundado por Tim Berners-Lee para auxlio no desenvolvimento de protocolos comuns para avaliao da Rede Mundial de Computadores. O Wiki foi criado em 1995 pelo Repositrio Padro de Portland, nos Estados Unidos, e um banco de dados aberto edio, permitindo que qualquer usurio possa atualizar e adicionar informao, criar novas pginas, etc., na Internet. Nesse mesmo ano, a S UN M ICROSYSTEMS lana a linguagem de programao orientada a objetos Java, amplamente utilizada hoje em dia para criar aplicaes para a Internet. Os rudimentos
Richard Matthew Stallman (), nascido nos Estados Unidos, fsico, ativista poltico e ativista de software. Linus Benedict Torvalds (), nascido na Finlndia, cientista da computao. 55 Jerry Chih-Yuan Yang (), nascido em Taiwan, engenheiro eltrico e empresrio. 56 David Filo (?), nascido nos Estados Unidos, engenheiro da computao e empresrio.
54 53
DCT
UFMS
1.2 S CULO XX
16
da ferramenta de busca Google so desenvolvidos em 1996 como um projeto de pesquisa dos alunos de doutorado Larry Page57 e Sergey Brin58 da Universidade de Stanford. Em 1998 a pgina do Google disponibilizada na Internet e, atualmente, a ferramenta de busca mais utilizada na rede. A WebTV tambm disponibilizada em 1996 possibilitando aos usurios navegar pela Internet a partir de sua TV. Com relao aos dispositivos eletrnicos, o Barramento Serial Universal USB59 padronizado em 1995 pela I NTEL, C OMPAQ, M ICROSOFT, entre outras. O USB um barramento externo padronizado que permite transferncia de dados e capaz de suportar at 127 dispositivos perifricos. Em 1997 o mercado comea a vender uma nova mdia de armazenamento tico de dados, o Disco Verstil Digital DVD60 , com as mesmas dimenses do CD, mas codicado em um formato diferente com densidade muito mais alta, o que permite maior capacidade de armazenamento. Os DVDs so muito utilizados para armazenar lmes com alta qualidade de som e vdeo. Em 1998 o primeiro tocador de MP3, chamado de MPMan, vendido no Japo pela empresa S AEHAN. Os processadores tambm foram vendidos como nunca nos anos 90. A I NTEL lana em 1993 o sucessor do INTEL 486, conhecido como Pentium, ou Pentium I, de 32 bits, 60 MHz e 3,1 milhes de transistores em sua verso bsica. A partir de 1997, a I NTEL lana seus processadores seguidamente, ano aps ano. Em 1997 anuncia a venda dos processadores Pentium MMX e Pentium II. O Celeron produzido a partir de 1998 e em 1999, a I NTEL anuncia o incio da produo do Pentium III. O Pentium IV produzido a partir de 2000 e um processador de 32 bits, 1,4 GHz e 42 milhes de transistores. A AMD j produzia microprocessadores desde a dcada de 70, mas entrou em forte concorrncia com a I NTEL a partir dessa poca, com o lanamento do K5 em 1995, concorrente do Figura 1.16: Processador Pentium. Pentium I, de 32 bits, 75 MHz e 4,3 milhes de transistores em sua primeira verso. Seguiu-se ao K5 o K6 em 1997, de 32 bits, 66 MHZ e 8,8 milhes de transistores, e a srie K7 de 1999, que engloba os processadores Athlon e Duron. A partir da dcada de 90, uma fatia considervel das fbricas produz computadores pessoais com processadores da I NTEL ou da AMD. No incio da dcada de 90, A PPLE, IBM e M OTOROLA se unem para projetar o processador PowerPC de 32 bits que equipou o PowerMac da A PPLE a partir de 1994. A aceitao desse novo computador no foi como o esperado e, aps um perodo de baixas, a A PPLE ressurge em 1998 com o iMac, um computador pessoal com projeto visual arrojado e com a losoa do tudo-em-um-s. A dcada de 90 marcada pela facilidade de acesso aos computadores pessoais, j que muitos deles passam a ser vendidos por menos que 1.000 dlares.
57
Lawrence Edward Page (), nascido nos Estados Unidos, engenheiro da computao e empresrio. Sergey Brin (), nascido na Rssia, matemtico, cientista da computao e empresrio. 59 Do ingls Universal Serial Bus. 60 Do ingls Digital Versatile Disc.
58
DCT
UFMS
17
O ano de 2000 inicia receoso para governos e grandes empresas pelo medo das conseqncias potencialmente desastrosas da virada do sculo nos sistemas computacionais, o que se chamou na poca de Bug do Milnio. Alm disso, o juiz Thomas Peneld anuncia a diviso do imprio M ICROSOFT em duas companhias e Bill Gates deixa o cargo de Chefe-Executivo da empresa. A M ICROSOFT lana o Windows XP em 2001 e recentemente o Windows Vista, em janeiro de 2007. A A PPLE disponibiliza o MacOS X 10.0 em 2001 e seguidas novas atualizaes at 2006, com o MAC OS X 10.5. O Linux tem maior penetrao e aceitao dos usurios de computadores pessoais e diversas distribuies so disponibilizadas como Ubuntu, Mandriva Linux, Fedora Core e SUSE Linux. Estas distribuies e os projetos comunitrios Debian e Gentoo, montam e testam os programas antes de disponibilizar sua distribuio. Atualmente, existem centenas de projetos de distribuio Linux em ativo desenvolvimento, constantemente revisando e melhorando suas respectivas distribuies. Em 2001 a D ELL passa a ser a maior fabricante de computadores pessoais do mundo. Em 2002, uma empresa de consultoria norte-americana faz uma estimativa que at aquele ano aproximadamente 1 bilho de computadores pessoais tinham sido vendidos no mundo inteiro desde sua consolidao. A I NTEL tem investido mais recentemente sua produo em processadores de 64 bits com ncleos mltiplos, como por exemplo o Xeon de 2004, o Pentium D de 2005 e o INTEL Core Duo de 2006. A AMD produz os processadores de 64 bits Duron em 2000 e o Sempron em 2004, ambos da srie K7, e o Opteron em 2003 da srie K8, este ltimo um processador de dois ncleos, para competir com os processadores de mltiplos ncleos, como o Xeon da I NTEL. A A PPLE, que vinha utilizando o processador PowerPC em sua linha de iMacs, anuncia que a partir de 2006 comear a usar os processadores da I NTEL. A verso de 2006 do iMac usa o processador INTEL Core 2 Duo. Na lista dos 500 supercomputadores mais rpidos do mundo, a TOP500, de novembro de 2006, 22,6% dos supercomputadores eram baseados em processadores Opteron da AMD e 21,6% em processadores Xeon da I NTEL.
DCT
UFMS
1.2 S CULO XX
18
Alguns modelos de computadores experimentais e inovadores tm sido propostos, com implementaes. O computador de DNA, o computador molecular, o computador qumico e o computador quntico so exemplos de propostas recentes e promissoras.
Exerccios
1.1 Visite a pgina do Computer History Museum, que contm uma enorme quantidade de informaes. 1.2 Escolha um ou dois personagens da pr-histria da computao e leia os verbetes sobre esses personagens na Wikipedia. 1.3 Leia os verbetes associados s duas mulheres Augusta Ada Byron e Grace Murray Hopper, que deram origem a muito do que conhecemos como computao. 1.4 Leia o verbete sobre Alan Mathison Turing, o Pai da Cincia da Computao, na Wikipedia. 1.5 Visite a pgina GNU Operating System e leia o verbete GNU na Wikipedia. 1.6 Visite as pgina Free Software Foundation e Fundao Software Livre Amrica Latina. 1.7 Leia o verbete Linux na Wikipedia.
DCT
UFMS
A ULA 2
C OMO
FUNCIONA UM COMPUTADOR
Na aula 1 vimos um pequeno histrico sobre os computadores. Nesta aula de hoje, vamos mencionar brevemente como essas mquinas so organizadas internamente. Como veremos, John von Neumann foi o responsvel, em 1945, pelo desenvolvimento de um modelo1 de computador que sobrevive at hoje. Este texto baseado principalmente nas referncias [10, 12].
2.1 Contextualizao
As primeiras mquinas computacionais projetadas tinham programas xos. Assim, realizavam apenas aquelas tarefas para as quais foram concebidas. Modicar o programa de uma dessas mquinas signicava reestruturar toda a mquina internamente. Mesmo para computadores como o ENIAC, programar e reprogramar era um processo geralmente laborioso, com uma quantidade enorme de rearranjo de ao e de cabos de conexo. Dessa forma, podemos destacar que a programao dos computadores que foram projetados antes do EDVAC tinha uma caracterstica curiosa e comum: a programao por meios externos. Ou seja, os programadores, para que essas mquinas executassem seus programas, deviam usar cartes perfurados, tas perfuradas, cabos de conexo, entre outros. A pequena disponibilidade de memria para armazenamento de dados e resultados intermedirios de processamento tambm representava uma outra diculdade complicadora importante. Em 1936, Alan Turing publicou um trabalho [16]2 que descrevia um computador universal terico, conhecido hoje como mquina universal de Turing. Esse computador tinha capacidade innita de armazenamento, onde se poderia armazenar dados e instrues. O termo usado foi o de computador com armazenamento de programas3 . Sobre esse mesmo conceito, o engenheiro alemo Konrad Zuse, que projetou a famlia Z de computadores, escreveu um trabalho [19]4 independentemente em 1936. Tambm de forma independente, John Presper Eckert e John Mauchly, que projetaram o ENIAC na Universidade da Pensilvnia, propuseram um trabalho em 1943, conforme meno em [11], sobre o conceito de computadores com armazenamento de programas. Em 1944, no projeto do novo computador EDVAC, John Eckert deixou registrado que sua equipe estava propondo uma forma inovadora de armazenamento de dados e programas em um dispositivo de memria enderevel, feito de mercrio, conhecido como linhas de atraso. John von Neumann, tendo trabalhado com a equipe que construiu o ENIAC, juntouVeja o verbete von Neumann architecture na Wikipedia. Artigo disponvel na seo Bibliograa complementar na pgina da disciplina no Moodle. 3 Do ingls stored-program computer. 4 Veja mais em The Life and Work of Konrad Zuse, especialmente a parte 10.
2 1
19
2.2 A RQUITETURA
DE VON
N EUMANN
20
se tambm s discusses do projeto desse novo computador e cou responsvel por redigir o documento com a descrio dessas idias. Com a publicao desse trabalho [17]5 , divulgado contendo apenas seu nome, essa organizao interna de um computador passou a ser conhecida como arquitetura de von Neumann, apesar de Turing, Zuse, Eckert e Mauchly terem contribudo ou j apresentado idias semelhantes. Esse trabalho obteve grande circulao no meio acadmico e seu sucesso como proposta de implementao de um novo computador foi imediato. At nossos dias, essa organizao ainda usada nos projetos de computadores mais conhecidos.
11111111111111111 00000000000000000 00000000000000000 11111111111111111 00000000000000000 11111111111111111 memria 00000000000000000 11111111111111111 00000000000000000 11111111111111111 00000000000000000 11111111111111111 111111111 000000000 UCP 00 11 000 111 000000000 111111111 00 11 000 111 000000000 111111111 00 11 000 111 000000000 111111111 UC ULA 00 11 000 111 000000000 111111111 00 11 000 111 000000000 111111111 000000000 111111111 000000000 111111111 11111 00000 00000 11111 00000 11111 00000 11111 entrada 00000 11111 00000 11111 00000 11111 00000 11111 1111 0000 0000 1111 0000 1111 0000 1111 sada 0000 1111 0000 1111 0000 1111 0000 1111
Figura 2.1: Arquitetura de von Neumann. A unidade central de processamento responsvel pela execuo das instrues armazenadas, tambm chamadas de programa. Atualmente, o termo UCP quase um sinnimo de microprocessador. Um microprocessador a implementao de uma UCP atravs de um nico,
5 6
Artigo disponvel na seo Bibliograa complementar na pgina da disciplina no Moodle. Do ingls central processing unit CPU.
UFMS
DCT
2.2 A RQUITETURA
DE VON
N EUMANN
21
ou de muito poucos, circuitos integrados. Diversos fabricantes disponibilizam microprocessadores no mercado, como os Core 2 Duo e Core 2 Quad da Intel e os Phenom X3 triple-core e Phenom X4 quad-core da AMD. A unidade central de processamento composta tambm pela unidade lgico-aritmtica e pela unidade de controle. A ULA executa dois tipos de operaes: (i) operaes aritmticas, como adies e subtraes, e (ii) operaes lgicas ou de comparao, como por exemplo a vericao se um nmero maior ou igual a outro nmero. A memria de um computador pode ser vista como uma lista linear de compartimentos ou clulas. Cada compartimento tem um identicador ou endereo numrico e pode armazenar uma certa quantidade pequena de informao. A informao armazenada em um compartimento de memria pode ser uma instruo de um programa ou um dado, uma informao que deve ser processada atravs das instrues. H dois tipos distintos de memria: (i) memrias volteis e (ii) memrias no volteis. As memrias do primeiro tipo necessitam de uma fonte de energia para que seu contedo seja mantido. As memrias que permitem acesso aleatrio7 aos seus compartimentos so, em geral, memrias volteis. Esses dispositivos permitem acesso rpido informao armazenada, mas so muito mais caros que os dispositivos no volteis de memria. O que em geral chamamos de memria principal de um computador um dispositivo de memria voltil, pois quando desligamos o computador, todas as informaes armazenadas em seus compartimentos so perdidas. Os dispositivos de armazenamento de informaes no volteis so aqueles que, ao contrrio dos primeiros, podem reter a informao armazenada mesmo sem uma fonte de energia conectada. Como exemplo desses dispositivos, podemos listar os discos rgidos, discos ticos, tas magnticas, memrias do tipo ash, memrias hologrcas, entre outros. Pela forma como em geral so implementados, esses dispositivos no permitem que as informaes sejam acessadas rapidamente em um compartimento qualquer, o que acarreta um tempo maior para busca e recuperao das informaes. No entanto, o custo de produo de tais dispositivos inferior ao custo dos dispositivos de memrias volteis. Se enxergarmos um computador como uma caixa-preta que alimentamos com informaes e colhemos resultados dessas informaes processadas, podemos associar os seus dispositivos de entrada e sada como aqueles que fazem a comunicao do computador com o mundo externo. O mundo externo pode ser composto por pessoas ou mesmo outros computadores. Teclados, mouses, microfones, cmeras, entre outros, so dispositivos comuns de entrada. Monitores e impressoras so dispositivos de sada. Placas de redes so dispositivos tanto de entrada como de sada de informaes. Um computador funciona ento como uma mquina que, a cada passo, carrega uma instruo e dados da memria, executa essa instruo sobre esses dados e armazena os resultados desse processamento em sua memria. Ento o processo se repete, isto , o computador novamente carrega uma prxima instruo e novos dados da memria, executa essa instruo sobre os dados e grava os resultados desse processamento em sua memria. Enquanto existirem instrues a serem executadas em um programa, esse processo se repete. Todo computador tem um sinal de relgio8 que marca esse passo mencionado, que mais conhecido como ciclo do relgio. Dessa forma, o sinal de relgio de um computador usado para coordenar e sincronizar as suas aes.
7 8
DCT
UFMS
2.3 A LGORITMOS
E PROGRAMAS
22
Uma outra representao a arquitetura de von Neumann, mais prxima ao que conhecemos como um computador pessoal, pode ser vista na gura 2.2.
impressora mouse teclado
1111 0000 0000 1111 0000 1111 portas 0000 1111 0000 1111 1111 0000 0000 1111 0000 1111 UCP 0000 1111 0000 1111 1111 0000 0000 1111 memria 0000 1111 0000 1111
1 0 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
barramento
1 0 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
111 000 000 111 placa 000 111 000 111 de disco 000 111 111 000 000 111 placa 000 111 000 111 grca 000 111 111 000 placa 000 111 000 111 de som 000 111 111 000 placa 000 111 000 111 de rede 000 111
disco rgido
DVD-RW
monitor
caixas de som
internet
a Khw Mohammad ebne Mus arazm , ( ), nascido na Prsia, matemtico, astrnomo e gegrafo.
UFMS
DCT
2.3 A LGORITMOS
E PROGRAMAS
23
Um algoritmo ento um conjunto de idias abstratas para soluo de um problema. Lidamos com algoritmos desde nossa infncia em diversas situaes. Por exemplo, aprendemos no ensino primrio o algoritmo de Euclides para obteno do mximo divisor comum entre dois nmeros inteiros. Nesse algoritmo, tomamos dois nmeros inteiros a e b e se b = 0 ento o mximo divisor comum entre a e b o nmero a. Caso contrrio, o mximo divisor comum entre a e b dado pelo mximo divisor comum de b e do resto da diviso de a por b. O processo ento se repete at que b seja igual a 0 (zero). importante destacar que esse algoritmo sempre obtm um b = 0 em algum dos passos e, portanto, o algoritmo sempre termina. Alm disso, o algoritmo correto, j que sempre produz uma resposta correta para um par de nmeros inteiros fornecidos como entrada. Aps o desenvolvimento de um algoritmo para soluo de algum problema, o prximo passo inform-lo a um computador. No entanto, os computadores no compreendem, e portanto so incapazes de obedecerem e executarem, instrues em uma linguagem natural como o portugus. Dessa forma, precisamos traduzir nosso algoritmo para um programa em uma linguagem de programao escolhida. Uma linguagem de programao uma linguagem articial projetada para expressar computaes que podem ser executadas por um computador. Dessa forma, um programa projetado em uma linguagem de programao possui uma sintaxe e semntica precisas. Alm disso, um programa escrito em uma linguagem de programao pode ser facilmente traduzido para um programa em linguagem de mquina, que nada mais que uma seqncia de dgitos binrios (bits) que representam dados e instrues e que produzem o comportamento desejado do computador, em sua arquitetura especca. Em geral, trabalhamos com linguagens de programao chamadas de alto nvel, o que signica que so linguagens de programao mais facilmente compreensveis para os seres humanos. As linguagens C, C++, Java, PHP, entre outras, so linguagens de programao de alto nvel. Em contraposio, existem linguagens de programao de baixo nvel, que so muito prximas da arquitetura do computador que usamos. A linguagem assembly uma linguagem de programao de baixo nvel. Infelizmente, programas escritos em uma linguagem programao qualquer no esto prontos para serem executados por um computador. Ainda h processos de traduo intermedirios que levam o programa de um ponto compreensvel por um ser humano e incompreensvel para o computador para o outro extremo, isto , basicamente incompreensvel pelo ser humano e completamente compreensvel pelo computador e sua arquitetura. Assim, dado um programa em uma linguagem de programao de alto nvel, usamos um programa especial, chamado compilador, para efetuar a traduo deste programa para um programa em linguagem assembly, de baixo nvel e associada ao processador do computador que ir executar as instrues. Um segundo e ltimo processo de traduo ainda realizado, chamado de montagem, onde h a codicao do programa na linguagem assembly para um programa em formato binrio, composto por 0s e 1s, e que pode enm ser executado pela mquina. Um esquema dos passos desde a concepo de um algoritmo at a obteno de um programa executvel equivalente em um computador mostrado na gura 2.4. Modelos conceituais inovadores e alternativos arquitetura de von Neumann tm sido propostos e alguns deles implementados. O computador de DNA, o computador molecular, o computador qumico e o computador quntico so exemplos de propostas recentes e promissoras.
DCT
UFMS
2.3 A LGORITMOS
E PROGRAMAS
24
111111 000000 000000 111111 000000 111111 algoritmo 000000 111111 000000 111111 0 1 000000 111111 0 1 0 1 0 1 0 programao 1 0 1 000000 111111 0 1 000000 111111 programa na 000000 111111 000000 111111 linguagem 000000 111111 X 000000 111111 000000 111111 0 1 0 1 0 1 0 compilao 1 0 1 000000 111111 0 1 000000 111111 000000 111111 programa 000000 111111 assembly 000000 111111 000000 111111 0 1 000000 111111 0 1 0 1 0 1 montagem 0 1 0 1 0 1 000000 111111 programa em 000000 111111 000000 111111 linguagem 000000 111111 de mquina 000000 111111 000000 111111
Figura 2.4: Passos desde a concepo de um algoritmo at o programa executvel por um computador.
Exerccios
2.1 Leia o verbete Computer na Wikipedia. 2.2 Leia o verbete von Neumann architecture na Wikipedia. 2.3 D uma olhada no trabalho de John von Neumann na referncia [17]10 , sobre a arquitetura de von Neumann. 2.4 D uma olhada no trabalho de Alan Turing na referncia [16]11 com a descrio da mquina de Turing. 2.5 Visite a pgina sobre a vida e o trabalho de Konrad Zuse. 2.6 Visite as pginas C History e C Programming Language e leia o verbete C (programming language) na Wikipedia.
10 11
Artigo disponvel na seo Bibliograa complementar na pgina da disciplina no Moodle. Artigo disponvel na seo Bibliograa complementar na pgina da disciplina no Moodle.
UFMS
DCT
A ULA 3
D ICAS
INICIAIS
Nesta aula aprenderemos dicas importantes sobre as ferramentas que mais usaremos durante o desenvolvimento da disciplina. Em particular, veremos a denio de uma interface do sistema operacional (shell ou Unix shell) e aprenderemos a usar alguns dentre os tantos comandos disponveis para essa interface, aprenderemos alguns comandos e funes do ambiente de trabalho GNU/Emacs e aprenderemos a usar o compilador da linguagem C da coleo de compiladores GNU, o gcc.
25
3.1 I NTERFACE
DO SISTEMA OPERACIONAL
26
Isso posto, um dos programas mais comuns disponveis para os usurios do sistema operacional GNU/Linux chamado de terminal, como uma abreviao de terminal de linhas de comando, que nada mais que uma interface entre o usurio e o sistema operacional. Para disparar esse programa no gerenciador de janelas GNOME, selecione a opo Aplicaes do menu principal, depois a opo Acessrios no sub-menu e, em seguida, a opo Terminal. Um exemplo de uma janela de terminal de linhas de comando do GNOME apresentado na gura 3.1.
Figura 3.1: Uma interface, ou janela, de linhas de comandos do sistema de janelas chamada no computador Homer pelo usurio fhvm.
GNOME,
Uma janela de linha de comandos possibilita a interao entre o usurio e o computador, intermediada pelo sistema operacional, onde o primeiro solicita tarefas diretamente ao outro e, depois de executadas, recebe seus resultados. Daqui por diante, veremos alguns dos comandos e utilitrios que sero mais usados durante o decorrer da disciplina. Alguns deles so apresentam, at certo ponto, algum grau de diculdade de compreenso neste momento. Todavia, importante ressaltar que todos esses comandos e utilitrios sero muito teis a partir de momentos oportunos, como veremos. A seguir, apresentamos a tabela 3.1 contendo os principais comandos e utilitrios do sistema operacional, com suas descries breves, os quais mais usaremos no desenvolvimento da disciplina de Programao de Computadores I. Esta tabela apenas um guia de referncia rpida e, apesar da maioria dos comandos e utilitrios serem intuitivos e muito fceis de usar, a descrio breve contida na tabela infelizmente no nos ensina de fato como fazer uso correto de cada um deles. Tambm no mostra todas as opes de execuo que em geral todo comando ou utilitrio possui.
DCT
UFMS
3.1 I NTERFACE
DO SISTEMA OPERACIONAL
27
Dessa forma, para aprender gradativa e corretamente a usar os comandos e utilitrios apresentados na tabela 3.1 necessrio: (i) fazer os exerccios ao nal desta aula; (ii) usar com freqncia o utilitrio man ou info , que so interfaces para o manual de referncia dos comandos e ferramentas do sistema1 . Por exemplo, podemos digitar:
prompt$ man comando
ou
prompt$ info comando
onde prompt$ o sinal de pronto do sistema, man e info so utilitrios do sistema e comando o nome do comando/ferramenta do sistema para o qual informaes do manual sero exibidas; e (iii) usar a opo --help na chamada de um comando/utilitrio. Essa opo mostra como us-lo corretamente, listando as opes disponveis. Por exemplo,
prompt$ man --help
Comando
cat arq cd dir cmp arq1 arq2 cp arq1 arq2 cp arq(s) dir date diff arq1 arq2 echo args enscript arq fmt arq gprof arq indent arq info cmd ls arq(s) ls dir man cmd mkdir dir(s) mv arq1 arq2 mv arq(s) dir pwd pr arq rm arq(s) rmdir dir(s) sort arq(s) wc arq(s) who
Descrio
mostra o contedo de arq ou da entrada padro troca o diretrio atual para o diretrio dir compara arq1 e arq2 byte a byte copia o arq1 para o arq2 copia arq(s) para o diretrio dir mostra a data e a hora do sistema compara arq1 e arq2 linha a linha imprime args converte arquivos-texto para PostScript, HTML, RTF, ANSI e outros formata arq fornece um perl de execuo do programa arq faz a indentao correta de um programa na linguagem C em arq mostra a pgina (mais completa) de manual de cmd lista arq(s) lista os arquivos no diretrio dir ou no diretrio atual mostra a pgina de manual de cmd cria diretrio(s) dir(s) move/renomeia o arq1 para o arq2 move arq(s) para o diretrio dir mostra o nome do diretrio atual converte arq, do tipo texto, para impresso remove arq(s) remove diretrios vazios dir(s) ordena as linhas de arq(s) ou da entrada padro conta o nmero de linhas, palavras e caracteres em arq(s) ou na entrada padro imprime a identicao do usurio do computador
Esses manuais tambm podem ser encontrados em pginas da internet, como Manpages e SS64.com.
DCT
UFMS
3.1 I NTERFACE
DO SISTEMA OPERACIONAL
28
Para aumentar a exibilidade e facilidade na especicao de argumentos de comandos do sistema operacional, podemos usar caracteres especiais de substituio, chamados de coringas. Como veremos, os coringas so muito usados quando os argumentos dos comandos so nomes de arquivos. O caractere * (asterisco) um coringa que substitui zero ou mais caracteres. No comando apresentado a seguir:
prompt$ ls *.c prompt$ o sinal de pronto do sistema, ls um comando que permite listar os arquivos de um diretrio e *.c um argumento desse comando que representa todos os arquivos do diretrio cujos nomes terminam com os caracteres .c .
O caractere ? (ponto de interrogao) um outro coringa muito usado como parte de argumentos de comandos, representando exatamente um caractere e que pode ser um caractere qualquer. Por exemplo, no comando abaixo:
prompt$ ls ???.c prompt$ o sinal de pronto do sistema, ls um comando que permite listar os arquivos de um diretrio e ???.c um argumento desse comando que representa todos os arquivos do diretrio cujos nomes contm exatamente 3 caracteres iniciais quaisquer seguidos pelos caracteres .c .
Os colchetes [ ] tambm so caracteres coringas muito usados nos argumentos de comandos e indicam a substituio de um nico caractere especicado no interior dos colchetes. No exemplo abaixo:
prompt$ ls *[abc]
o argumento *[abc] representa todos os arquivos do diretrio cujos nomes terminam com o caractere a , b ou c . Um intervalo de caracteres pode ser obtido no interior dos colchetes se o caractere - usado. Por exemplo:
prompt$ ls *[a-z]
onde o argumento representa os nomes dos arquivos que terminam com uma das 26 letras do alfabeto. Por m, importante destacar que muitos comandos do sistema operacional recebem informaes de entrada a partir do terminal e tambm enviam as informaes resultantes de sada para o terminal de comandos. Um comando em geral l as informaes de entrada de um local chamado de entrada padro, que pr-denido como o terminal de linha de comandos, e escreve sua sada em um local chamado de sada padro, que tambm pr-denido como o terminal de linhas de comando. Por exemplo, o comando sort pode ordenar nomes fornecidos linha a linha por um usurio no terminal de comandos, ordenar esses nomes lexicogracamente e apresentar o resultado no terminal de linha de comandos:
DCT UFMS
3.1 I NTERFACE
DO SISTEMA OPERACIONAL
29
prompt$ sort
Se dispomos os caracteres > arq ao nal de qualquer comando ou utilitrio que normalmente envia sua sada para a sada padro, ou seja, para o terminal de linha de comandos, o sada desse comando ser escrita para o arquivo com nome arq ao invs do terminal. Por exemplo, a seqncia a seguir:
prompt$ sort > nomes.txt
faz com que quatro nomes digitados pelo usurio sejam ordenados e, em seguida, armazenados no arquivo nomes.txt . Assim como a sada dos comandos e utilitrios pode ser redirecionada para um arquivo, a entrada tambm pode ser redirecionada a partir de um arquivo. Se, por exemplo, existe um arquivo temp.txt contendo diversos nomes apresentados linha a linha, ento o comando a seguir:
prompt$ sort < temp.txt
faz com que todos os nomes contidos no arquivo temp.txt sejam ordenados lexicogracamente e posteriormente apresentados seguida na sada padro, isto , na janela do terminal de linha de comandos. Podemos ainda redirecionar a entrada e a sada de um comando concomitantemente. Por exemplo, o comando:
prompt$ sort < temp.txt > nomes.txt
recebe, pelo redirecionamento da entrada, os nomes contidos no arquivo temp.txt , ordenaos lexicogracamente e os armazena em um novo arquivo chamado nomes.txt , devido ao redirecionamento da sada. Dessa forma, temos que os smbolos < e > so os smbolos de redirecionamento de entrada e sada, respectivamente.
DCT UFMS
3.2 C OMPILADOR
30
3.2 Compilador
A coleo de compiladores GNU (GCC) , na verdade, um arcabouo de compiladores para diversas linguagens de programao. Esse sistema foi desenvolvido pelo Projeto GNU e, por ter sido adotado como o compilador ocial do sistema operacional GNU, tambm foi adotado como compilador padro por muitos dos sistemas operacionais derivados do Unix, tais como o GNU/Linux, a famlia BSD e o Mac OS X. Alm disso, o GCC tem sido traduzido para uma grande variedade de arquiteturas. Richard Stallman desenvolveu a primeira verso do GCC em 1987, como uma extenso de um compilador j existente da linguagem C. Por isso, naquela poca o compilador era conhecido como GNU C Compiler. No mesmo ano, o compilador foi estendido para tambm compilar programas na linguagem C++. Depois disso, outras extenses foram incorporadas, como Fortran, Pascal, Objective C, Java e Ada, entre outras. A Fundao para o Software Livre distribui o GCC sob a Licena Geral Pblica (GNU GPL GNU General Public License). O GCC um software livre. Um compilador faz o trabalho de traduzir um programa na linguagem C, ou de outra linguagem de alto nvel qualquer, para um programa em uma linguagem intermediria, que ainda ser traduzido, ou ligado, para um programa em uma linguagem de mquina. Dessa forma, um argumento bvio que devemos fornecer ao GCC o nome do arquivo que contm um programa na linguagem C. Alm disso, podemos informar ao compilador um nome que ser atribudo ao programa executvel resultante da compilao e ligao. H tambm diversas opes disponveis para realizar uma compilao personalizada de um programa e que podem ser informadas no processo de compilao. Ento, o formato geral de compilao de um programa dado a seguir:
prompt$ gcc programa.c -o executvel -Wall -ansi -pedantic
onde gcc o programa compilador, programa.c um arquivo com extenso .c que contm um programa fonte na linguagem C, -o uma opo do compilador que permite fornecer um nome ao programa executvel resultante do processo ( executvel ), -Wall uma opo que solicita que o compilador mostre qualquer mensagem de erro que ocorra durante a compilao, -ansi uma opo que fora o programa fonte a estar escrito de acordo com o padro ANSI da linguagem C e -pedantic uma opo que deixa o compilador muito sensvel a qualquer possvel erro no programa fonte. No momento, ainda no escrevemos um programa na linguagem C e, por isso, no conseguimos testar o compilador de forma satisfatria. Esta seo ento apenas um guia de referncia rpida que ser muito usada daqui por diante.
3.3 Emacs
Em geral, usamos a palavra Emacs para mencionar um editor/processador de textos, mas Emacs , na verdade, o nome de uma classe de editores/processadores que se caracteriza especialmente pela sua extensibilidade. O Emacs tem mais de 1.000 comandos de edio, mais do que qualquer outro editor existente, e ainda permite que um usurio combine esses comandos em macros para automatizao de tarefas. A primeira verso do Emacs foi escrita em 1976
DCT UFMS
3.3 E MACS
31
por Richard Stallman. A verso mais popular do Emacs o GNU/Emacs, uma parte do Projeto GNU. O GNU/Emacs um editor extensvel, congurvel, auto-documentado e de tempo real. A maior parte do editor escrita na linguagem Emacs Lisp, um dialeto da linguagem de programao funcional Lisp. O Emacs considerado por muitos especialistas da rea como o editor/processador de textos mais poderoso existente nos dias de hoje. Sua base em Lisp permite que se torne congurvel a ponto de se transformar em uma ferramenta de trabalho completa para escritores, analistas e programadores. Em modo de edio, o Emacs comporta-se como um editor de texto normal, onde o pressionamento de um caractere alfanumrico no teclado provoca a insero do caractere correspondente no texto, as setas movimentam o ponto ou cursor de edio, a tecla Backspace remove um caractere, a tecla Insert troca o modo de insero para substituio ou vice-versa, etc. Comandos podem ser acionados atravs do pressionamento de uma combinao de teclas, pressionando a tecla Ctrl e/ou o Meta/Alt juntamente com uma tecla normal. Todo comando de edio de fato uma chamada de uma funo no ambiente Emacs Lisp. Alguns comandos bsicos so mostrados na tabela 3.2. Observe que a tecla Ctrl representada por C e a tecla Alt por M . Um manual de referncia rpida est disponvel na bibliograa complementar na pgina da disciplina. Para carregar o Emacs, podemos usar o menu principal do GNOME e escolher a opo Aplicaes, escolhendo em seguida a opo Acessrios e ento a opo Emacs. Ou ento, de uma linha de comandos, podemos usar:
prompt$ emacs &
Vejamos a tabela 3.2, que contm um certo nmero de comandos2 importantes. Teclas C-z C-x C-c C-x C-f C-x C-s C-x i C-x C-w C-h r C-h t C-g C-s C-r M-% Descrio minimiza a janela do Emacs naliza a execuo do Emacs carrega um arquivo no Emacs salva o arquivo atual no disco insere o contedo de um outro arquivo no arquivo atual salva o contedo do arquivo em um outro arquivo especicado carrega o manual do Emacs mostra um tutorial do Emacs aborta o comando parcialmente informado busca uma cadeia de caracteres a partir do cursor busca uma cadeia de caracteres antes do cursor interativamente, substitui uma cadeia de caracteres
Tabela 3.2: Uma pequena tabela de comandos do Emacs. importante observar que o Emacs, quando executado em sua interface grca, tem a possibilidade de executar muitos de seus comandos atravs de botes do menu ou atravs da barra de menus. No entanto, usurios avanados preferem usar os atalhos de teclado para esse tipo de execuo, por terem um acesso mais rpido e mais conveniente depois da memorizao dessas seqncias de teclas.
2
Veja tambm o carto de referncia do Emacs na seo de bibliograa complementar da disciplina no Moodle.
UFMS
DCT
3.3 E MACS
32
Exerccios
3.1 Abra um terminal. A partir de seu diretrio inicial, chamado de home pelo sistema operacional, faa as seguintes tarefas: crie trs diretrios com os seguintes nomes: progi, algodadi e temp; crie trs subdiretrios dentro do diretrio progi com os seguintes nomes: aulas, trabalhos e provas; usando o comando cat e o smbolo de redirecionamento de sada > , crie um arquivo com nome agenda.txt, que contm linha a linha, nome e telefone de pessoas. Adicione pelo menos 10 nomes nesse arquivo; verique o contedo dos diretrios progi/aulas, algodadi e temp; copie o arquivo agenda.txt para os diretrios algodadi e temp; entre no diretrio aulas; entre no diretrio progi;
use o comando sort para ordenar o contedo do arquivo agenda.txt. Execute o comando duas vezes: na primeira, use-o visualizando o resultado na tela do computador. Na segunda, redirecione a sada desse mesmo comando, criando um novo arquivo com nome agenda-ordenada.txt; verique o contedo do diretrio; compare os arquivos agenda.txt e agenda-ordenada.txt com os comandos cmp e diff ; crie um arquivo com nome frase contendo uma frase qualquer, com pelo menos 20 caracteres. Da mesma forma como antes, use o comandos cat e o smbolo > de redirecionamento de sada; conte o nmero de palavras no arquivo frase usando o comando wc ; neste diretrio, liste todos os arquivos que comeam com a letra f; neste diretrio, liste todos os arquivos que comeam com a letra a;
neste diretrio, liste todos os arquivos que terminam com a seqncia de caracteres txt; repita os trs ltimos comandos, usando no entanto o comando echo ; liste todos os arquivos contidos neste diretrio, mas redirecione a sada do comando para um arquivo de nome lista;
execute o comando ls [afl]* e verique a sada; execute o comando ls [a-l]* e verique a sada; execute o comando ls ?[gr]* e verique a sada; execute o comando ls ????? e verique a sada; execute o comando ls .. e verique a sada; execute o comando ls ?g* e verique a sada;
DCT
UFMS
3.3 E MACS
33
mova os arquivos cujos nomes tm exatamente 5 caracteres neste diretrio para o diretrio temp; remova todos os arquivos criados at aqui, mantendo apenas os diretrios. 3.2 Abra o Emacs. Ento, realize as seguintes tarefas: digite um texto que resume o que voc aprendeu nas aulas de Programao de Computadores I at aqui; busque as palavras computador, Turing, von Neumann, ENIAC, ls, cp e Emacs no seu texto; troque a palavra Emacs no seu texto pela palavra GNU/Emacs. salve o arquivo no diretrio progi/aulas; remova o diretrio temp do seu sistema de arquivos;
DCT
UFMS
A ULA 4
P RIMEIROS
PROGRAMAS
4.1 Digitando
Abra o seu Emacs, pressione C-x C-f , digite em seguida um nome para seu primeiro programa, como por exemplo primeiro.c, e ento digite o seguinte cdigo. Programa 4.1: Primeiro programa.
1 2 3 4 5 6
Depois de ter digitado o programa, pressione as teclas C-x C-s para salv-lo em um diretrio adequado da sua rea de trabalho. Na linguagem C, letras minsculas e maisculas so diferentes. Alm disso, em programas na linguagem C no h distino de onde voc inicia a digitao das suas linhas e, assim, usamos essa caracterstica a nosso favor, adicionando espaos em algumas linhas do programa para facilitar sua leitura. Essa adio de espaos em uma linha chamada indentao ou tabulao. No Emacs, voc pode usar a tecla Tab para adicionar indentaes de forma adequada. Veremos que a indentao uma prtica de programao muito importante.
34
4.3 O LHANDO
35
Depois disso, o programa executvel a.out estar disponvel para execuo no mesmo diretrio. Ento s digitar a.out (ou ./a.out) na linha de comando do terminal para ver o resultado da execuo:
prompt$ a.out
Programar bacana!
prompt$
O nome a.out um nome padro que o compilador gcc d aos arquivos executveis resultantes de uma compilao. Em geral, atribumos um nome mais signicativo a um programa executvel, como por exemplo o mesmo nome do programa na linguagem C, mas sem a sua extenso. No exemplo acima, o programa executvel associado tem o nome primeiro. O compilador gcc pode gerar um executvel dessa forma como segue:
prompt$ gcc primeiro.c -o primeiro prompt$
ser muito provavelmente includa em todo programa que voc far na linguagem C. Essa linha fornece informaes ao compilador sobre a funo de sada de dados de nome printf , que usada depois no programa. A segunda linha do programa
int main(void)
informa ao compilador onde o programa inicia de fato. Em particular, essa linha indica que main , do ingls principal, o incio do programa principal e, mais que isso, que esse trecho do programa na verdade uma funo da linguagem C que no recebe valores de entrada ( void ) e devolve um valor inteiro ( int ). Esses conceitos de funo, parmetros de entrada de uma funo e valor de sada sero elucidados oportunamente. Por enquanto, devemos memorizar que essa linha indica o incio de nosso programa. A prxima linha contm o smbolo abre-chave { que estabelece o incio do bloco de comandos do programa. Em seguida, a prxima linha contm uma chamada funo printf :
printf("Programar bacana!\n");
Essa funo tem um nico argumento ou parmetro, que a seqncia de smbolos da tabela ASCII printf("Programar bacana!\n"); . Note que todos os smbolos dessa seqncia so mostrados na sada, a menos de \n , que tem um signicado especial quando sua impresso solicitada por printf : saltar para a prxima linha. A funo printf uma rotina
DCT UFMS
4.4 P RXIMO
PROGRAMA
36
da biblioteca stdio.h da linguagem C que mostra o seu argumento na sada padro, que geralmente o monitor. Depois disso, adicionamos a linha
return 0;
que termina a execuo do programa. Finalmente o smbolo fecha-chave } indica o nal do bloco de comandos do programa.
#include <stdio.h> int main(void) { int num1, num2, soma; num1 = 25; num2 = 30; soma = num1 + num2; printf("A soma de %d e %d %d\n", num1, num2, soma); return 0; }
Este exemplo ligeiramente diferente do primeiro e introduz algumas novidades. Agora, a primeira linha aps a linha que contm o nome da funo main apresenta uma declarao de variveis. Trs variveis do tipo inteiro so declaradas: num1 , num2 e soma . Isso signica que, durante a execuo desse programa, trs compartimentos de memria so reservados pelo computador para armazenamento de informaes que, neste caso, so nmeros inteiros. Alm disso, a cada compartimento associado um nome: num1 , num2 e soma . Esses compartimentos de memria so tambm conhecidos como variveis, j que seu contedo pode variar durante a execuo de um programa. Depois disso, na prxima linha do nosso programa temos uma atribuio do valor do tipo inteiro 25 para a varivel num1 . Observe ento que o smbolo de atribuio da linguagem C = . Na linha seguinte, uma outra atribuio realizada, do nmero 30 para a varivel num2 . Na prxima linha temos uma atribuio para a varivel soma . No entanto, note que no temos mais um nmero no lado direito da expresso de atribuio, mas sim a expresso aritmtica num1 + num2 . Neste caso, durante a execuo dessa linha do programa, o computador consulta o contedo das variveis num1 e num2 , realiza a operao de adio com os dois valores obtidos dessas variveis e, aps isso, atribui o resultado varivel soma . No nosso exemplo, o resultado da expresso aritmtica 25 + 30, ou seja, o valor 55, ser atribudo varivel soma . A linha seguinte de nosso programa contm uma chamada funo printf , mas agora com
DCT UFMS
4.5 D OCUMENTAO
37
quatro argumentos: o primeiro agora uma cadeia de caracteres de formatao, contendo no apenas caracteres a serem impressos na sada, mas smbolos especiais, iniciados com % , conhecidos como conversores de tipo da linguagem C. Neste caso, os smbolos %d permitem que um nmero inteiro seja mostrado na sada. Note que trs conversores %d esto contidos na seqncia de smbolos de sada e, a cada um deles e na ordem como so apresentados est associado uma das variveis num1 , num2 e soma , que so os argumentos restantes da funo printf .
4.5 Documentao
Uma boa documentao de um programa, conforme [4], signica inserir comentrios apropriados no cdigo de modo a explicar o que cada uma das funes que compem o programa faz. A documentao de uma funo um pequeno manual que d instrues precisas e completas sobre o uso da funo. Comentrios so ento introduzidos em programas com o objetivo de document-los e de incrementar a sua legibilidade. O(a) programador(a) responsvel por manter seus cdigos legveis e bem documentados para que, no futuro, possa retom-los e compreend-los sem muito esforo e sem desperdcio de tempo. Na linguagem C padro, os comentrios so envolvidos pelos smbolos /* e */ . Um comentrio completamente ignorado quando encontrado pelo compilador e, portanto, no faz diferena alguma no programa executvel produzido pelo compilador da linguagem. A seguir, mostramos nosso ltimo programa desta aula com comentrios explicativos adicionados nos pontos onde so realmente necessrios. Programa 4.3: Segundo programa com comentrios explicativos.
1 2 3 4 5 6 7 8 9 10 11 12
#include <stdio.h> /* Esta funo faz a adio de dois nmeros inteiros fixos e mostra o resultado da operao na sada. */ int main(void) { int num1, num2, soma; num1 = 25; num2 = 30; soma = num1 + num2; printf("A soma de %d e %d %d\n", num1, num2, soma); return 0; }
Ainda conforme [4], uma boa documentao no se preocupa em explicar como uma funo faz o que faz, mas sim o que ela faz de fato, informando quais so os valores de entrada da funo, quais so os valores de sada e quais as relaes que esses valores que entram e saem da funo e as transformaes pela funo realizadas.
DCT
UFMS
4.5 D OCUMENTAO
38
Exerccios
4.1 Escreva uma programa na linguagem C que escreva a seguinte mensagem na sada padro:
a) b) c) d) e) Comentrios na linguagem C iniciam com /* e terminam com */ Letras minsculas e maisculas so diferentes na linguagem C A palavra chave main indica o incio do programa na linguagem C Os smbolos { e } envolvem um bloco de comandos na linguagem C Todos os comandos na linguagem C devem terminar com um ponto e vrgula
4.2 Qual a sada esperada para o programa 4.4? Programa 4.4: Programa do exerccio 4.2.
1 2 3 4 5 6 7 8 9
#include <stdio.h> int main(void) { printf("Al! "); printf("Al! "); printf("Tem algum a?"); printf("\n"); return 0; }
4.3 Escreva um programa na linguagem C que subtraia 14 de 73 e mostre o resultado na sada padro com uma mensagem apropriada. 4.4 Verique se o programa 4.5 est correto. Em caso negativo, liste os erros de digitao que voc encontrou. Programa 4.5: Programa do exerccio 4.4.
1 2 3 4 5 6 7 8 9
include <stdio.h> /* computa a soma de dois nmeros Int main(void) { int resultado; resultado = 13 + 22 - 7 printf("O resultado da operao %d\n" resultado); return 0; }
DCT
UFMS
A ULA 5
E NTRADA
E SADA
A funo usada para entrada de dados scanf e a funo usada para sada de dados printf sero estudadas nesta aula. Estas so funes bsicas da linguagem C que fazem a interao com usurio durante a execuo de um programa, solicitando valores de entrada e mostrando valores na sada.
5.1 Um exemplo
Vamos ver um primeiro exemplo de programa na linguagem C que usa a funo de leitura
scanf . Abra seu Emacs, pressione C-x C-f , d um nome para seu programa, como por exemplo soma.c , e digite o cdigo apresentado no programa 5.1.
#include <stdio.h> /* Programa que l dois nmeros inteiros e mostra o resultado da soma desses dois nmeros */ int main(void) { int num1, num2, soma; printf("Informe um nmero: "); scanf("%d", &num1); printf("Informe outro nmero: "); scanf("%d", &num2); soma = num1 + num2; printf("A soma de %d mais %d %d\n", num1, num2, soma); return 0; }
Vamos olhar mais de perto este programa. Observe que, pela primeira vez, usamos a funo
scanf . Desconsiderando o esqueleto do programa, a primeira linha a ser notada a declara-
o de variveis:
int num1, num2, soma;
39
5.2 S EGUNDO
EXEMPLO
40
Estamos, com esta linha do programa, reservando trs compartimentos de memria que podero ser utilizados para armazenamento de nmeros inteiros, cujos nomes desses compartimentos so num1 , num2 e soma . Em seguida, h uma funo para escrita e uma funo para leitura:
printf("Informe um nmero: scanf("%d", &num1); ");
A funo para escrita printf j bem conhecida, vista nas aulas anteriores. A funo de leitura scanf tem dois parmetros: uma cadeia de caracteres de formatao "%d" e uma varivel num1 correspondente a este formato. Diferentemente da funo printf , a varivel na funo scanf deve vir precedida com o smbolo & . No comando acima, a cadeia de caracteres de formatao "%d" indica que o valor informado pelo usurio ser convertido para um inteiro. Ainda, este valor ser ento armazenado na varivel do tipo inteiro num1 . Observe, mais uma vez, que esta varivel precedida por um & . A prxima seqncia de instrues equivalente e solicita ao usurio do programa a entrada de um outro nmero, que ser armazenado na varivel num2 :
printf("Informe outro nmero: scanf("%d", &num2) ; ");
Este um comando de atribuio da linguagem C, determinado pelo smbolo = . Um comando de atribuio tem uma sintaxe bem denida: do lado esquerdo do smbolo de atribuio = h sempre uma varivel; do lado direito h uma constante, uma varivel ou uma expresso do mesmo tipo da varivel esquerda. Este comando funciona da seguinte forma: avalia a expresso do lado direito do smbolo de atribuio e atribui o resultado desta avaliao para a varivel do lado esquerdo. Em nosso exemplo, o lado direito uma expresso aritmtica de adio, que soma os contedos das variveis do tipo inteiro num1 e num2 . O resultado da avaliao dessa expresso um nmero inteiro que ser atribudo varivel soma , do lado esquerdo do smbolo de atribuio. Por m, a chamada funo
printf("A soma de %d mais %d %d\n", num1, num2, soma);
mostra, alm dos valores das duas parcelas num1 e num2 , o resultado dessa soma armazenado na varivel soma .
5.2 S EGUNDO
EXEMPLO
41
#include <stdio.h> /* A funo main l dois nmeros inteiros e mostra na sada o resultado da multiplicao desses dois nmeros */ int main(void) { int num1, num2, produto; printf("Informe um nmero: "); scanf("%d", &num1); printf("Informe outro nmero: "); scanf("%d", &num2); produto = num1 * num2; printf("O produto de %d por %d %d\n", num1, num2, produto); return 0; }
Exerccios
5.1 Faa um programa que leia trs nmeros inteiros a, b e c, calcule a b + c e mostre o resultado na sada padro para o usurio. Programa 5.3: Uma soluo possvel para o exerccio 5.1.
1 2 3 4 5 6 7 8 9
#include <stdio.h> int main(void) { int a, b, c; printf("Informe 3 nmeros inteiros: "); scanf("%d %d %d", &a, &b, &c); printf("A expresso %d*%d+%d tem resultado %d.\n", a, b, c, a*b+c); return 0; }
5.2 Faa um programa que leia um nmero inteiro e mostre o seu quadrado e seu cubo. Por exemplo, se o nmero de entrada 3, a sada deve ser 9 e 27. 5.3 Faa um programa que leia trs nmeros inteiros e mostre como resultado a soma desses trs nmeros e tambm a multiplicao desses trs nmeros. 5.4 Escreva um programa que leia um nmero inteiro e mostre o resultado da quociente (inteiro) da diviso desse nmero por 2 e por 3.
DCT
UFMS
A ULA 6
E STRUTURAS
CONDICIONAIS
Nesta aula veremos alguns pequenos exemplos de programas que usam estruturas condicionais. Alm da estrutura seqencial, aquela que tivemos contato nas aulas anteriores, a estrutura condicional uma ferramenta de programao importante que auxilia o programador a decidir que trechos de programa devem ser executados ou no, dependendo da avaliao da condio expressa. Para maior compreenso das estruturas condicionais certamente necessrio o entendimento dos conceitos de constantes, variveis, expresses aritmticas e expresses lgicas da linguagem C. Por enquanto, esses conceitos caro implcitos, j que vamos estud-los com bastante detalhe na aula 7.
para n 1. No caso em que n = 1, os delimitadores de bloco, isto , as chaves { e } , so opcionais. Uma deciso tomada de acordo com a avaliao da condio , que uma expresso lgica: caso o resultado dessa avaliao seja verdadeiro, o comando ou o bloco de comandos ser executado; caso contrrio, ser ignorado. Se o nmero de comandos a serem executados em um bloco interno estrutura condicional pelo menos dois, ento esse bloco tem de vir envolvido por chaves. 42
6.2 E STRUTURA
CONDICIONAL COMPOSTA
43
Vejamos um pequeno exemplo no programa 6.1. Programa 6.1: Um exemplo contendo uma estrutura condicional simples.
1 2 3 4 5 6 7 8 9 10 11
#include <stdio.h> int main(void) { int idade; printf("Quantos anos voc tem? "); scanf("%d", &idade); if (idade < 30) printf("Nossa! Voc bem jovem!\n"); printf("At breve!\n"); return 0; }
Neste programa 6.1, uma nica varivel idade do tipo inteiro declarada no incio do programa. Em seguida, solicitado ao usurio do programa 6.1 que informe um nmero inteiro, que ser armazenado na varivel idade . Se o valor informado pelo usurio for um nmero inteiro menor que 30, ento uma mensagem Nossa! Voc bem jovem! aparecer no monitor. Por m, a mensagem At breve! impressa.
if (condio) { comando 1; comando 2; ... comando m; } else { comando 1; comando 2; ... comando n; }
para m , n 1. No caso em que m = 1 ou n = 1, os delimitadores de bloco, isto , as chaves { e } , so opcionais. Ento, vejamos um outro exemplo no programa 6.2.
DCT
UFMS
6.3 T ROCA
DE CONTEDOS
44
#include <stdio.h> int main(void) { int idade; printf("Quantos anos voc tem? "); scanf("%d", &idade); if (idade < 30) printf("Nossa! Voc bem jovem!\n"); else printf("Puxa! Voc j velhinho!\n"); printf("At breve!\n"); return 0; }
Observe agora que no programa 6.2 as mensagens que podem ser emitidas para o usurio so excludentes.
#include <stdio.h> int main(void) { int x, y, aux; printf("Informe o valor de x: "); scanf("%d", &x); printf("Informe o valor de y: "); scanf("%d", &y); if (x > y) { aux = x; x = y; y = aux; } printf("%d menor ou igual a %d\n", x, y); return 0; }
DCT
UFMS
6.3 T ROCA
DE CONTEDOS
45
Na verdade, o programa 6.3 l dois valores do tipo inteiro e os armazena nas variveis x e y e, em seguida, armazena o menor desses dois valores em x e o maior em y . Se houver empate, a mensagem emitida est correta. Suponha que o programa 6.3 est sendo executado e que os valores de x e y j foram lidos. Observe primeiro que se um usurio desse programa informa um valor para x e um valor para y de tal forma que o valor de x menor ou igual ao valor de y ento a avaliao da condio x > y tem resultado falso e o bloco de comandos interno estrutura condicional no executado. Agora suponha o contrrio, isto , suponha que o usurio do programa informa um valor para x maior que o valor informado para y . Ento, a avaliao da condio x > y tem resultado verdadeiro, o bloco de comandos interno estrutura condicional executado e uma troca ser realizada: o contedo da varivel x armazenado temporariamente na varivel aux , a varivel x recebe o contedo da varivel y e, nalmente, a varivel y recebe o contedo da varivel aux . Note que a troca no poderia ser realizada sem o uso de uma varivel auxiliar. A ltima instruo do programa a chamada funo de impresso printf para mostrar o contedo das variveis x e y . Por exemplo, suponha que um usurio do programa 6.3 fornea o valor 2 para x e 5 para y . Ento, a troca no realizada e a mensagem impressa na sada padro 2 menor ou igual a 5 . Se, ao contrrio, o usurio fornece 5 para x e 2 para y , a troca realizada e a mensagem na sada padro tambm 2 menor ou igual a 5 .
Exerccios
6.1 Escreva um programa que receba a temperatura ambiente em graus Clsius e mostre uma mensagem para o usurio informando se a temperatura est muito quente. Considere como temperatura limite o valor de 30 graus Clsius. Programa 6.4: Uma possvel soluo para o exerccio 6.1.
1 2 3 4 5 6 7 8 9 10 11 12
#include <stdio.h> int main(void) { int temperatura; printf("Informe uma temperatura (em graus Celsius): "); scanf("%d", &temperatura); if (temperatura > 30) printf("Hoj um dia bem quente!\n"); else printf("Hoje no est to quente.\n"); return 0; }
6.2 Escreva um programa que receba trs valores, armazenando-os nas variveis x, y e z , e ordene esses valores de modo que, ao nal, o menor valor esteja armazenado na varivel x, o valor intermedirio esteja armazenado na varivel y e o maior valor esteja armazenado na varivel z .
DCT
UFMS
A ULA 7
N MEROS
INTEIROS
Nas aulas anteriores, nos programas que zemos, tomamos contato com a estrutura de programao seqencial, onde comandos de atribuio, chamadas de funes de entrada e sada e tambm declaraes de variveis so dispostos linha aps linha em um bloco de comandos envolvido por { e } . Vimos o uso de funes, com seus argumentos, tais como a funo para entrada de dados scanf e a funo para sada de dados printf . Tambm vimos as estruturas condicionais da linguagem C, onde o uxo de execuo de um programa pode ser modicado de acordo com a avaliao de uma expresso lgica. A partir da aula de hoje formalizaremos todos esses conceitos. Iniciamos essa formalizao com os nmeros inteiros, denindo constantes, variveis e expresses que envolvem esses nmeros. No nal desta aula, explicamos tambm como feita a representao de nmeros inteiros em um computador.
46
7.1 C ONSTANTES
E VARIVEIS
47
H uma regra fundamental sobre variveis e programao na linguagem C: qualquer varivel usada em um programa deve ser previamente declarada. H tambm regras para um programador determinar os identicadores de variveis na linguagem C. Essas regras so as apresentadas a seguir: os smbolos vlidos em um identicador de varivel so os caracteres do alfabeto da lngua inglesa, minsculos ou maisculos, os dgitos numricos e o sublinhado1 (_); o identicador de uma varivel deve iniciar com uma letra ou com um sublinhado (_); aps o primeiro caracter, o identicador de uma varivel determinado por qualquer seqncia de letras minsculas ou maisculas, de nmeros ou de sublinhados. Observe que as regras descritas acima excluem quaisquer caracteres que estejam fora do conjunto { a , . . . , z , A , . . . , Z } e isso signica que caracteres acentuados e cedilhas no podem fazer parte de identicadores de variveis. Dessa forma, os seguintes identicadores so nomes vlidos de variveis:
Por outro lado, os identicadores listados a seguir no so nomes vlidos de variveis na linguagem C:
No primeiro caso, o identicador preo$ contm dois smbolos no vlidos: e $. O segundo identicador tambm tem o mesmo problema, j que um identicador no pode conter um caracter espao. O terceiro exemplo tem um identicador que inicia com um caracteres no vlido, um dgio numrico. O ltimo exemplo um identicador no vlido j que int uma palavra reservada da linguagem C. Tambm importante destacar que letras minsculas e maisculas na linguagem C so diferentes. Assim, as variveis soma , Soma e SOMA so diferentes. A declarao de variveis do tipo inteiro na linguagem C pode ser realizada com um nico comando int e mais uma lista de identicadores de variveis, separados por um espao e
1
DCT
7.1 C ONSTANTES
E VARIVEIS
48
uma vrgula, e nalizada com um ponto e vrgula; ou ainda, pode ser realizada uma a uma, com um comando int para cada varivel:
ou
Os identicadores das variveis podem ser to longos quanto se queira, apesar de apenas os 63 primeiros caracteres serem signicativos para o compilador da linguagem C. Um programa escrito com identicadores de variveis muito longos pode ser difcil de ser escrito. Por exemplo,
Em um programa na linguagem C, os nmeros inteiros que ocorrem em expresses so conhecidos como constantes. Por exemplo, o nmero 37 representa um valor inteiro constante. Expresses aritmticas que se constituem apenas de valores constantes so chamadas de expresses aritmticas constantes. Por exemplo,
781 + 553 - 12 * 44
7.2 E XPRESSES
49
#include <stdio.h> int main(void) { int x, y, z, resultado; x = 25; y = 10; resultado = x + y; printf("A soma de %d e %d %d\n", x, y, resultado); x = 38; y = 29; resultado = x - y; printf("A substrao de %d e %d %d\n", x, y, resultado); x = 51; y = 17; resultado = x * y; printf("A multiplicao de %d por %d %d\n", x, y, resultado); x = 100; y = 25; resultado = x / y; printf("A diviso de %d por %d %d\n", x, y, resultado); x = 17; y = 3; resultado = x % y; printf("O resto da diviso de %d por %d %d\n", x, y, resultado); x = -7; resultado = -x; printf("%d com sinal trocado %d\n", x, resultado); x = 10; y = 4; z = 15; resultado = x + y * z; printf("A expresso %d + %d * %d tem resultado %d\n", x, y, z, resultado); return 0; }
DCT
UFMS
7.3 R EPRESENTAO
DE NMEROS INTEIROS
50
H ainda um operador unrio - na linguagem C, que trabalha sobre um nico valor e que troca o seu sinal, isto , troca o sinal de uma constante, uma varivel ou uma expresso aritmtica do tipo inteiro. No exemplo acima, o comando de atribuio x = -7; na linha 25 e o comando de atribuio resultado = -x; na linha 26 fazem com que o valor armazenado na varivel resultado seja 7 . Observe nalmente que o resultado da avaliao da expresso aritmtica 10 + 4 * 15 no 14 15 = 210, mas sim 10 + 60 = 70. Os operadores aritmticos na linguagem C tm precedncias uns sobre os outros. O operador de menos unrio precede todos os outros. Multiplicaes, divises e restos de divises tm precedncia sobre a adio e subtrao e, por isso, o resultado da operao
resultado = x + y * z;
dado primeiro pela avaliao da multiplicao e depois pela avaliao da adio. Parnteses so utilizados para modicar a ordem das operaes. Por exemplo,
resultado = (x + y) * z;
teria como resultado da avaliao o valor 210. Segue um resumo das precedncias dos operadores aritmticos binrios sobre nmeros inteiros: Operador * / % + Descrio Multiplicao, diviso e resto da diviso Adio e subtrao Precedncia 1 (mxima) 2 (mnima)
Na linguagem C, os nmeros inteiros positivos e negativos so conhecidos como inteiros com sinal. Para declarar uma varivel i do tipo inteiro com sinal, temos de digitar
DCT UFMS
Uma seqncia de bits chamada de um nmero binrio ou nmero na base 2. Por exemplo, 010110 um nmero binrio representado por uma seqncia de 6 bits. Podemos converter um nmero binrio em um nmero decimal, ou nmero na base 10, que nada mais que um nmero natural. Por exemplo, o nmero binrio 010110 representa o nmero decimal 22, pois 0 25 + 1 24 + 0 23 + 1 22 + 1 21 + 0 20 = 22.
7.3 R EPRESENTAO
DE NMEROS INTEIROS
51
int i;
Cada varivel do tipo int armazenada em b bytes consecutivos na memria, onde b dependente da arquitetura do computador. A maioria dos computadores pessoais atuais tm b = 4 e assim cada varivel do tipo int representada por uma seqncia de 8 4 = 32 bits, perfazendo um total de 232 valores possveis, contidos no conjunto 231 , . . . , 1, 0, 1, . . . , 231 1 ou 2147483648, . . . , 1, 0, 1, . . . , 2147483647 . Cada seqncia de 32 bits que comea com 0 representa um int no-negativo em notao binria. Cada seqncia de 32 bits que comea com 1 representa o inteiro negativo k 232 , onde k o valor da seqncia em notao binria. Esta representao de nmeros inteiros negativos chamada de complemento-de-dois. Nmeros inteiros que estejam fora do intevalo 231 , 231 1 so representados mdulo 232 .
Ressaltamos ainda que todos os valores acima podem ser obtidos usando frmulas gerais em funo do nmero de bytes consecutivos usados para representar um int .
Exerccios
7.1 Escreva um programa que receba um nmero inteiro x e avalie o polinmio p(x) = 3x3 5x2 + 2x 1 . Programa 7.2: Possvel soluo para o exerccio 7.1.
1 2 3 4 5 6 7 8 9 10
#include <stdio.h> int main(void) { int x, p; printf("Informe x: "); scanf("%d", &x); p = 3 * x * x * x - 5 * x * x + 2 * x - 1; printf("p(%d) = %d.\n", x, p); return 0; }
7.2 Para transformar um nmero inteiro i no menor inteiro m maior que i e mltiplo de um nmero inteiro j , a seguinte frmula pode ser utilizada: m = i + j i mod j ,
DCT UFMS
7.3 R EPRESENTAO
DE NMEROS INTEIROS
52
onde o operador mod o operador de resto de diviso inteira na notao matemtica usual, que corresponde ao operador % na linguagem C. Por exemplo, suponha que usamos i = 256 dias para alguma atividade e queremos saber qual o total de dias m que devemos ter de forma que esse nmero seja divisvel por j = 7, para termos uma idia do nmero de semanas que usaremos na atividade. Ento, pela frmula acima, temos que m = 256 + 7 256 mod 7 = 259 . = 256 + 7 4
Escreva um programa que receba dois nmeros inteiros positivos i e j e devolva o menor inteiro m maior que i e mltiplo de j .
DCT
UFMS
A ULA 8
E STRUTURA
DE REPETIO
while
Estruturas de repetio so, juntamente com a estrutura seqencial e as estruturas condicionais, elementos fundamentais em algoritmos e programao. So essas as estruturas bsicas que permitem que um programador consiga resolver centenas e centenas de problemas de maneira efetiva. Nesta aula motivaremos o estudo das estruturas de repetio e apresentaremos a estrutura de repetio while da linguagem C.
8.1 Motivao
Suponha que algum queira escrever um programa para imprimir os dez primeiros nmeros inteiros positivos. Este problema, apesar de muito simples e pouco til primeira vista, motiva a introduo das estruturas de repetio, como veremos adiante. Com o que aprendemos at o momento, bem fcil escrever um programa como esse. Programa 8.1: Um programa para imprimir os 10 primeiros inteiros positivos.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
#include <stdio.h> int main(void) { printf("1\n"); printf("2\n"); printf("3\n"); printf("4\n"); printf("5\n"); printf("6\n"); printf("7\n"); printf("8\n"); printf("9\n"); printf("10\n"); return 0; }
Alm de bem simples, o programa 8.1 parece estar realmente correto, isto , parece realizar o propsito de imprimir os dez primeiros nmeros inteiros positivos de forma correta. Mas e se quisssemos imprimir os 1.000 primeiros nmeros inteiros positivos? Um programa seme53
8.2 E STRUTURA
DE REPETIO
while
54
lhante ao programa acima caria um pouco mais complicado e montono de escrever, j que teria muitas e muitas linhas de cdigo. Uma das propriedades fundamentais dos computadores que eles possuem a capacidade de executar repetitivamente um conjunto de comandos. Essa capacidade de repetio permite que um programador escreva programas mais concisos que contenham processos repetitivos que poderiam necessitar de centenas ou milhares de linhas se no fossem assim descritos. A linguagem de programao C tem trs estruturas de repetio diferentes. A seguir veremos a estrutura while .
#include <stdio.h> int main(void) { int numero; numero = 1; while (numero <= 100) { printf("%d\n", numero); numero = numero + 1; } return 0; }
A novidade neste programa a estrutura de repetio while . O programa tem incio com a declarao da varivel numero que pode armazenar nmeros inteiros. A linha 5 contm um comando de atribuio que faz com que a constante numrica 1 seja armazenada na varivel numero . Depois disso, na linha 6 temos a estrutura de repetio while . A estrutura de repetio while composta por um conjunto de elementos: (i) inicializao da varivel que controla a estrutura de repetio; (ii) condio, ou expresso lgica, que controla a repetio do bloco de comandos da estrutura; (iii) seqncia de comandos, que formam o bloco de comandos, a menos da ltima linha, da estrutura de repetio e que sero executados; e (iv) passo, que , em geral, a ltima linha do bloco de comandos da estrutura de repetio e determina a freqncia com que o bloco de comandos ser executado. No programa 8.2, a inicializao realizada antes da estrutura de repetio while , como mostrado na linha 5, com o comando de atribuio numero = 1; . A condio, ou expresso lgica, que sempre vem envolvida por parnteses e aparece logo aps o comando while , dada numero <= 100 . A seqncia de comandos composta apenas pelo comando printf("%d\n", numero); . E o passo vem, em geral, aps a seqncia de comandos. No caso do nosso programa o passo um incremento, numero = numero + 1 .
DCT UFMS
8.2 E STRUTURA
DE REPETIO
while
55
O funcionamento da estrutura de repetio while dado da seguinte forma. Sempre que um programa encontra o comando while a expresso lgica envolvida por parnteses que vem logo a seguir avaliada. Se o resultado da avaliao for verdadeiro, o bloco de comandos interno a essa estrutura ser executado. Ao nal da seqncia de comandos, o uxo de execuo volta ao comando while para nova avaliao da sua expresso lgica. Se novamente o resultado da avaliao dessa expresso for verdadeiro, seu bloco de comandos repetido. Esse processo termina quando o resultado da avaliao da expresso lgica falso. Quando isso acontece, o uxo de execuo do programa salta para a prxima linha aps o bloco de comandos da estrutura de repetio while . No caso do nosso programa, a linha alcanada por esse salto a linha 10. Vamos fazer agora um programa que soma os 100 primeiros nmeros inteiros positivos. Veja o programa 8.3. Programa 8.3: Programa que soma os 100 primeiros nmeros inteiros positivos.
1 2 3 4 5 6 7 8 9 10 11 12 13
#include <stdio.h> int main(void) { int numero, soma; soma = 0; numero = 1; while (numero <= 100) { soma = soma + numero; numero = numero + 1; } printf("A soma dos 100 primeiros inteiros positivos %d\n", soma); return 0; }
Exerccios
8.1 Dado um inteiro positivo n, somar os n primeiros inteiros positivos. 8.2 Dado n, imprimir os n primeiros naturais mpares. Exemplo: Para n = 4 a sada dever ser 1, 3, 5, 7. 8.3 Dado um inteiro positivo n, calcular n!. 8.4 Dado n, imprimir as n primeiras potncias de 2. Exemplo: Para n = 5 a sada dever ser 1, 2, 4, 8, 16. 8.5 Dados x inteiro e n um natural, calcular xn .
DCT UFMS
8.2 E STRUTURA
DE REPETIO
while
56
#include <stdio.h> int main(void) { int n, numero, soma; printf("Informe n: "); scanf("%d", &n); soma = 0; numero = 1; while (numero <= n) { soma = soma + numero; numero = numero + 1; } printf("O resultado da soma dos %d primeiros inteiros %d\n", n, soma); return 0; }
8.6 Dado um inteiro positivo n e uma seqncia de n inteiros, somar esses n nmeros.
DCT
UFMS
A ULA 9
E XERCCIOS
Enunciados
9.1 Dado um inteiro positivo n e uma seqncia de n nmeros inteiros, determinar a soma dos nmeros inteiros positivos da seqncia. Exemplo: Se n = 7 e a seqncia de nmeros inteiros 6, 2, 7, 0, 5, 8, 4 a sada deve ser 19. Programa 9.1: Soluo para o exerccio 9.1.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
#include <stdio.h> int main(void) { int n, conta, num, soma; printf("Informe n: "); scanf("%d", &n); soma = 0; conta = 1; while (conta <= n) { printf("Informe um nmero: "); scanf("%d", &num); if (num > 0) soma = soma + num; conta = conta + 1; } printf("A soma dos nmeros positivos da seqncia %d.\n", soma); return 0; }
9.2 Dado um inteiro positivo n e uma seqncia de n inteiros, somar os nmeros pares e os nmeros mpares. 57
58 9.3 Durante os 31 dias do ms de maro foram tomadas as temperaturas mdias dirias de Campo Grande, MS. Determinar o nmero de dias desse ms com temperaturas abaixo de zero. 9.4 Dado um inteiro positivo n e uma seqncia de n inteiros, determinar quantos nmeros da seqncia so positivos e quantos so no-positivos. Um nmero no-positivo se negativo ou se igual a 0 (zero). 9.5 Dado um inteiro positivo n e uma seqncia de n inteiros, determinar quantos nmeros da seqncia so pares e quantos so mpares. 9.6 Uma loja de discos anota diariamente durante o ms de abril a quantidade de discos vendidos. Determinar em que dia desse ms ocorreu a maior venda e qual foi a quantidade de discos vendida nesse dia. 9.7 Dados o nmero n de estudantes de uma turma de Programao de Computadores I e suas notas de primeira prova, determinar a maior e a menor nota obtidas por essa turma, onde a nota mnima 0 e a nota mxima 100.
DCT
UFMS
A ULA 10
E XERCCIOS
Enunciados
10.1 Dado um nmero inteiro positivo n e dois nmeros naturais no nulos i e j , imprimir em ordem crescente os n primeiros naturais que so mltiplos de i ou de j ou de ambos. Exemplo: Para n = 6, i = 2 e j = 3 a sada dever ser 0, 2, 3, 4, 6, 8. Programa 10.1: Soluo para o exerccio 10.1.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
#include <stdio.h> int main(void) { int n, i, j, numero, contador; printf("Informe os nmeros n, i e j: "); scanf("%d %d %d", &n, &i, &j); numero = 0; contador = 1; while (contador <= n) { if (numero % i == 0) { printf("%d ", numero); contador = contador + 1; } else { if (numero % j == 0) { printf("%d ", numero); contador = contador + 1; } } numero = numero + 1; } printf("\n"); return 0; }
59
60
#include <stdio.h> int main(void) { int n, i, j, numero, contador; printf("Informe o nmero n: "); scanf("%d", &n); printf("Informe os nmeros i e j: "); scanf("%d %d", &i, &j); numero = 0; contador = 1; while (contador <= n) { if ((numero % i == 0) || (numero % j == 0)) { printf("%d ", numero); contador = contador + 1; } numero = numero + 1; } printf("\n"); return 0; }
10.2 Dizemos que um nmero natural triangular se produto de trs nmeros naturais consecutivos. Exemplo: 120 triangular, pois 4 5 6 = 120. Dado n natural, vericar se n triangular. 10.3 Dados dois nmeros inteiros positivos, determinar o mximo divisor comum entre eles utilizando o algoritmo de Euclides. Exemplo: 24 9 1 15 6 1 9 3 1 6 0 2 3 = mdc(24,15)
10.4 Dados dois nmeros inteiros positivos a e b, representando a frao a/b, escreva um programa que reduz a/b para uma frao irredutvel. Exemplo: Se a entrada 9/12 a sada tem de ser 3/4.
DCT
UFMS
61 10.5 Dados a quantidade de dias de um ms e o dia da semana em que o ms comea, escreva um programa que imprima os dias do ms por semana, linha a linha. Considere o dia da semana 1 como domingo, 2 como segunda-feira, e assim por diante, at o dia 7 como sbado. Exemplo: Se a entrada 31 e 3 ento a sada deve ser 1 8 15 22 29 2 9 16 23 30 3 10 17 24 31 4 11 18 25 5 12 19 26
6 13 20 27
7 14 21 28
DCT
UFMS
A ULA 11
E XPRESSES
COM INTEIROS
Nesta aula veremos expresses aritmticas, relacionais e lgicas com nmeros inteiros. Em aulas anteriores j tomamos contato com essas expresses, mas pretendemos formaliz-las aqui. A compreenso destas expresses com nmeros inteiros fundamental para que possamos escrever programas mais ecientes e poderosos e tambm porque podemos estender esse entendimento a outros tipos de variveis.
Suponha ainda que os seguintes comandos de atribuio tenham sido executados sobre essas variveis: 62
11.2 E XPRESSES
RELACIONAIS
63
De acordo com os valores das inicializaes das variveis envolvidas e o resultado da avaliao das prprias expresses, a avaliao de cada uma das linhas acima tem valores -7, 102, 4037 e 202, respectivamente. A tabela abaixo, contendo as prioridades dos operadores aritmticos unrios e binrios, j foi exibida em parte na aula 7. Operadores + * / % + Tipo unrios binrios binrios Descrio constante positiva, troca de sinal multiplicao, diviso e resto da diviso adio e subtrao Precedncia 1 (mxima) 2 3 (mnima)
Descrio igual a diferente de menor que maior que menor que ou igual a maior que ou igual a
UFMS
11.3 E XPRESSES
LGICAS
64
O resultado da avaliao de uma expresso relacional sempre um valor lgico, isto , verdadeiro ou falso. Assim, supondo que temos declarado trs variveis a , b e c do tipo inteiro como a seguir,
int a, b, c;
a = 2; b = 3; c = 4;
a a b b
== 2 > b + c + c <= 5 - a != 3
tm valores verdadeiro, falso, falso e falso, respectivamente. Uma observao importante neste momento que o tipo lgico ou booleano no existe na linguagem C. Isso signica que literalmente no existem constantes lgicas e variveis lgicas. O resultado da avaliao de uma expresso relacional ou lgica , na verdade, um valor numrico do tipo inteiro. Se este um valor diferente de zero, ento a expresso considerada ter valor verdadeiro. Caso contrrio, isto , se este valor igual a zero, ento a expresso tem valor falso. Dessa forma, representaremos em geral o valor 1 (um) para a constante lgica com valor verdadeiro e 0 (zero) para a constante lgica com valor falso. Lembrando apenas que, na verdade, qualquer valor diferente de 0 (zero) avaliado como verdadeiro em uma expresso lgica.
DCT
11.3 E XPRESSES
LGICAS
65
ltimo caso, relacionamos as proposies atravs de operadores lgicos. Os operadores lgicos da linguagem C utilizados como conectivos nas expresses lgicas so apresentados a seguir. Operador && || ! Descrio conjuno disjuno negao
Duas proposies p e q podem ser combinadas pelo conectivo && para formar uma nica proposio denominada conjuno das proposies originais: p && q e lemos p e q . O resultado da avaliao da conjuno de duas proposies verdadeiro se e somente se ambas as proposies tm valor verdadeiro, como mostra a tabela a seguir. p 1 1 0 0 q 1 0 1 0 p && q 1 0 0 0
Duas proposies p e q podem ser combinadas pelo conectivo || para formar uma nica proposio denominada disjuno das proposies originais: p || q e lemos p ou q , com sentido de e/ou. O resultado da avaliao da disjuno de duas proposies verdadeiro se pelo menos uma das proposies tem valor verdadeiro, como mostra a tabela a seguir. p 1 1 0 0 q 1 0 1 0 p || q 1 1 1 0
Dada uma proposio p , uma outra proposio, chamada negao de p , pode ser obtida atravs da insero do smbolo ! antes da proposio: !p e lemos no p . Se a proposio p tem valor verdadeiro, ento !p tem valor falso e se p tem valor falso, ento a proposio !p tem valor verdadeiro, como mostra a tabela a seguir. p 1 0 !p 0 1
Exemplos de expresses lgicas so mostrados a seguir. Inicialmente, suponha que declaramos as variveis do tipo inteiro a seguir:
int a, b, c, x;
11.3 E XPRESSES
LGICAS
66
a b c x
= = = =
2; 3; 4; 1;
5 a !x
tm valores verdadeiro, verdadeiro, falso na primeira coluna e verdadeiro, verdadeiro e falso na segunda coluna. Observe que avaliamos as expresses lgicas em uma ordem: primeiro avaliamos as expresses aritmticas, depois as expresses relacionais e, por m, as expresses lgicas em si. Por exemplo,
A tabela abaixo ilustra a prioridade de todos os operadores aritmticos, relacionais e lgicos, unrios e binrios, vistos at aqui. Operador + * / % + != >= <= ! && || Tipo unrios binrios binrios binrios unrio binrio binrio Precedncia 1 (mxima) 2 3 4 5 6 7 (mnima)
==
>
<
Observe que o operador de negao ! um operador unrio, ou seja, necessita de apenas um operando como argumento da operao. Os outros operadores so todos binrios. Lembre-se tambm que para modicar a precedncia de alguma expresso necessrio o uso de parnteses, como j vimos nas expresses aritmticas.
DCT UFMS
11.3 E XPRESSES
LGICAS
67
Dessa forma, se considerarmos as mesmas variveis e atribuies acima, a expresso lgica abaixo seria avaliada da seguinte forma:
x + c >= a + b || 2 * x < b && a > b + x 1 + 4 >= 2 + 3 || 2 * 1 < 3 && 2 > 3 + 1 5 >= 5 1 1 || || || 1 2 < 3 1 && 2 > 4 && 0 0
Observe que, como o operador lgico de conjuno && tem prioridade sobre o operador lgico de disjuno || , o resultado da expresso acima verdadeiro. Se tivssemos realizado a disjuno primeiramente, como poderamos intuitivamente supor devido ao posicionamento mais esquerda do operador de disjuno, o resultado da expresso seria diferente.
Exerccios
Alguns exerccios desta aula ensinam um importante truque de programao que , na verdade, o uso de uma varivel que simula um tipo lgico e que indica que algo ocorreu durante a execuo do programa. Essa varivel chamada de indicadora de passagem. 11.1 Dado p inteiro, vericar se p primo. 11.2 Dado um nmero inteiro positivo n e uma seqncia de n nmeros inteiros, vericar se a seqncia est em ordem crescente. 11.3 Dados um nmero inteiro n > 0 e um dgito d, com 0 o dgito d ocorre no nmero n. d 9, determinar quantas vezes
11.4 Dado um nmero inteiro positivo n, vericar se este nmero contm dois dgitos consecutivos iguais. 11.5 Dado um nmero inteiro positivo n, vericar se o primeiro e o ltimo dgito deste nmero so iguais.
DCT
UFMS
11.3 E XPRESSES
LGICAS
68
#include <stdio.h> int main(void) { int p, divisor; printf("Informe um nmero: "); scanf("%d", &p); divisor = 2; while (divisor <= p/2) { if (p % divisor == 0) divisor = p; else divisor = divisor + 1; } if (divisor == p/2 + 1) printf("%d primo\n", p); else printf("%d no primo\n", p); return 0; }
Programa 11.2: Soluo para o exerccio 11.1 usando uma varivel indicadora de passagem.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
#include <stdio.h> int main(void) { int p, divisor, primo; printf("Informe um nmero: "); scanf("%d", &p); divisor = 2; primo = 1; while (divisor <= p/2 && primo == 1) { if (p % divisor == 0) primo = 0; else divisor = divisor + 1; } if (primo == 1) printf("%d primo\n", p); else printf("%d no primo\n", p); return 0; }
DCT
UFMS
11.3 E XPRESSES
LGICAS
69
#include <stdio.h> int main(void) { int p, divisor, primo; printf("Informe um nmero: "); scanf("%d", &p); divisor = 2; primo = 1; while (divisor <= p/2 && primo) { if (! (p % divisor)) primo = 0; else divisor = divisor + 1; } if (primo) printf("%d primo\n", p); else printf("%d no primo\n", p); return 0; }
DCT
UFMS
A ULA 12
E STRUTURA
DE REPETIO
for
Relembrando, as estruturas de repetio so estruturas de programao fundamentais tanto para construo de algoritmos como de programas. Todas as linguagens de programao de alto nvel apresentam uma ou mais estruturas de repetio. De fato, so essas as estruturas bsicas que permitem que um programador resolva centenas e centenas de problemas. Nesta aula, alm de uma formalizao sobre as estruturas de programao, apresentaremos a estrutura de repetio for da linguagem C.
#include <stdio.h> int main(void) { int x, quadrado, cubo; printf("Informe um nmero: "); scanf("%d", &x); quadrado = x * x; cubo = quadrado * x; printf("O quadrado de %d %d\n", x, quadrado); printf("O cubo de %d %d\n", x, cubo); return 0; }
70
12.1 E STRUTURAS
DE PROGRAMAO
71
Observe que o programa 12.1 contm uma nica estrutura de programao, onde as linhas so dispostas uma aps outra e nalizadas com um ponto e vrgula ( ; ). Ou seja, aps a linha que determina o incio do programa principal, a nica estrutura de programao apresentada neste programa a estrutura seqencial. Tambm j vimos as estruturas condicionais, simples e compostas, de programao. Uma estrutura condicional realiza um desvio no uxo de execuo de um programa, condicionado ao valor resultante de uma expresso lgica. Vejamos um exemplo do uso de uma estrutura condicional no programa 12.2. Programa 12.2: Um exemplo de um programa contendo uma estrutura condicional composta. Note que a estrutura condicional est inserida na estrutura seqencial da funo main . E, alm disso, no interior da estrutura condicional h estruturas seqenciais.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
#include <stdio.h> int main(void) { int x, y, z; printf("Informe um nmero: "); scanf("%d", &x); printf("Informe outro nmero: "); scanf("%d", &y); if (x > y) { z = x * x; printf("O quadrado de %d %d\n", x, z); } else { z = y * y * y; printf("O cubo de %d %d\n", y, z); } return 0; }
Aps o incio do programa 12.2, seguem-se cinco linhas, na estrutura seqencial de programao, contendo primeiro a declarao das variveis e depois funes de entrada e sada. Depois disso, uma estrutura condicional composta apresentada e o uxo de execuo do programa ca sujeito expresso lgica u > v . Caso o resultado da avaliao dessa expresso seja verdadeiro, o bloco de comandos que vem logo a seguir ser executado. Caso contrrio, isto , se o resultado for falso, o bloco de comandos aps a palavra reservada else ser executado. Observe ainda que as estruturas de programao podem estar encaixadas umas dentro das outras, como pode ser observado no programa 12.2: o bloco de comandos aps o comando if , contendo duas linhas, apresentado na estrutura seqencial, assim como o bloco de comandos aps o comando else , que tambm contm duas linhas na estrutura seqencial de programao. Aps essa estrutura condicional, o programa nalizado com o ltimo comando ( return 0; ).
DCT
UFMS
12.2 E STRUTURA
DE REPETIO
for
72
E, nalmente, como vimos nas aulas anteriores mais recentes, as estruturas de repetio fazem com que um bloco de comandos seja executado enquanto uma condio satisfeita. Quando essa condio deixa de ser satisfeita, o uxo de execuo salta para a prxima linha aps este bloco. Este o caso da estrutura de repetio while que vimos nas ltimas aulas. Nesta aula, aprenderemos a estrutura de repetio for , muito semelhante estrutura de repetio while .
#include <stdio.h> int main(void) { int numero; for (numero = 1; numero <= 100; numero = numero + 1) printf("%d\n", numero); return 0; }
A novidade neste programa a estrutura de repetio for . Um esqueleto da estrutura de repetio for dado a seguir:
A estrutura de repetio for dispe a inicializao, a condio e o passo todos na mesma linha, logo aps o comando for , envolvidos por parnteses. O funcionamento deste comando dado da seguinte forma. Sempre que um comando for encontrado, a inicializao executada. Em seguida, a condio vericada. Se o resultado da avaliao desta condio verdadeiro, ento o uxo de execuo desviado para a seqncia de comandos no bloco de comandos interno estrutura for . Finalizada a seqncia de comandos, o passo realizado e o uxo de execuo desviado para a linha do comando for , onde novamente a condio ser testada e o processo todo repetido, at que esta condio tenha como resultado de sua
DCT
UFMS
12.2 E STRUTURA
DE REPETIO
for
73
avaliao o valor falso e o uxo da execuo ento desviado para o prximo comando aps o bloco de comandos do comando for . As semelhanas entre a estrutura de repetio for e while so destacadas a seguir.
inicializao while (condio) { seqncia de comandos passo } for (inicializao; condio; passo) { seqncia de comandos }
Vamos fazer um outro exemplo de programa, que tambm j zemos antes no programa 8.3, que soma os 100 primeiros nmeros inteiros positivos e utiliza a estrutura de repetio for . Vejamos o programa 12.4. Programa 12.4: Programa que soma os 100 primeiros inteiros positivos usando a estrutura de repetio for.
1 2 3 4 5 6 7 8 9 10
#include <stdio.h> int main(void) { int numero, soma; soma = 0; for (numero = 1; numero <= 100; numero = numero + 1) soma = soma + numero; printf("A soma dos 100 primeiros nmeros inteiros %d\n", soma); return 0; }
Aqui talvez seja um bom momento para entrarmos em contato com outros dois operadores aritmticos da linguagem C. Os operadores unrios de incremento ++ e de decremento -tm como funo adicionar ou subtrair uma unidade ao valor armazenado em seu operando, respectivamente. Mas infelizmente, a compreenso das formas de uso desses operadores pode ser facilmente prejudicada. Isso porque, alm de modicar os valores de seus operandos, ++ e -- podem ser usados como operadores prexos e tambm como operadores posxos. Por exemplo, o trecho de cdigo abaixo
int cont; cont = 1; printf("cont vale %d\n", ++cont); printf("cont vale %d\n", cont);
DCT
UFMS
12.2 E STRUTURA
DE REPETIO
for
74
imprime cont vale 2 e cont vale 2 na sada. Por outro lado, o trecho de cdigo abaixo
int cont; cont = 1; printf("cont vale %d\n", cont++); printf("cont vale %d\n", cont);
imprime cont vale 1 e cont vale 2 na sada. Podemos pensar que a expresso ++cont signica incremente cont imediatamente enquanto que a expresso cont++ signica use agora o valor de cont e depois incremente-o. O depois nesse caso depende de algumas questes, mas seguramente a varivel cont ser incrementada antes da prxima sentena ser executada. O operador -- tem as mesmas propriedades do operador ++ . importante observar que os operadores de incremento e decremento, se usados como operadores posxos, tm maior prioridade que os operadores unrios + e - . Se so usados como operadores prexos, ento tm a mesma prioridade dos operadores + e - . O programa 12.4, que soma os 100 primeiros nmeros inteiros positivos, pode ser reescrito como no programa 12.5. Programa 12.5: Programa que realiza a mesma tarefa do programa 12.4, mas usando o operador aritmtico unrio de incremento.
1 2 3 4 5 6 7 8 9 10
#include <stdio.h> int main(void) { int numero, soma; soma = 0; for (numero = 1; numero <= 100; ++numero) soma = soma + numero; printf("O resultado da soma dos 100 primeiros inteiros %d\n", soma); return 0; }
Exerccios
12.1 Dado um nmero natural na base binria, transform-lo para a base decimal. Exemplo: Dado 10010 a sada ser 18, pois 1 24 + 0 23 + 0 22 + 1 21 + 0 20 = 18.
DCT
UFMS
12.2 E STRUTURA
DE REPETIO
for
75
#include <stdio.h> int main(void) { int bin, dec, pot2; printf("Informe um nmero na base binria: "); scanf("%d", &bin); pot2 = 1; dec = 0; for ( ; bin != 0; bin = bin/10) { dec = dec + bin % 10 * pot2; pot2 = pot2 * 2; } printf("%d\n", dec); return 0; }
12.2 Dado um nmero natural na base decimal, transform-lo para a base binria. Exemplo: Dado 18 a sada dever ser 10010. 12.3 Dado um nmero inteiro positivo n que no contm um dgito 0, imprimi-lo na ordem inversa de seus dgitos. Exemplo: Dado 26578 a sada dever ser 87562. 12.4 Qualquer nmero natural de quatro algarismos pode ser dividido em duas dezenas formadas pelos seus dois primeiros e dois ltimos dgitos. Exemplos: 1297: 12 e 97. 5314: 53 e 14. Escreva um programa que imprima todos os nmeros de quatro algarismos cuja raiz quadrada seja a soma das dezenas formadas pela diviso acima. Exemplo: 9801 = 99 = 98 + 01. Portanto, 9801 um dos nmeros a ser impresso.
DCT
UFMS
A ULA 13
E STRUTURA
DE REPETIO
do-while
Nesta aula veremos uma terceira, e ltima, estrutura de repetio da linguagem C, a estrutura do-while . Diferentemente das estruturas de repetio while e for que estabelecem seus critrios de repetio logo no incio, a estrutura de repetio do-while tem um critrio de repetio ao nal do bloco de comandos associado a elas. Isso signica, entre outras coisas, que o bloco de comandos associado estrutura de repetio do-while executado pelo menos uma vez, diferentemente das outras duas estruturas de repetio, que podem ter seus blocos de comandos nunca executados.
#include <stdio.h> int main(void) { int numero, soma; soma = 0; do { printf("Informe um nmero: "); scanf("%d", &numero); soma = soma + numero; } while (numero != 0); printf("A soma dos nmeros informados %d\n", soma); return 0; }
76
13.1 D EFINIO
E EXEMPLO DE USO
77
O programa 13.1 muito semelhante aos demais programas que vimos construindo at o momento. A nica diferena signicativa o uso da estrutura de repetio do-while . Quando o programa encontra essa estrutura de programao pela primeira vez, com uma linha contendo a palavra reservada do seguida de um smbolo de incio de bloco de comandos { , o uxo de programao segue para o primeiro comando deste bloco. Os comandos desse bloco so ento executados um a um, at que o smbolo de m do bloco de comandos } seja encontrado. Logo em seguida, encontramos a palavra reservada while e, depois, uma expresso lgica envolvida por parnteses. Se o resultado da avaliao dessa expresso lgica for verdadeiro, ento o uxo de execuo do programa desviado para o primeiro comando do bloco de comandos desta estrutura de repetio e todos os comandos so novamente executados. Caso contrrio, o uxo de execuo desviado para o prximo comando aps a linha contendo o nal desta estrutura de repetio, isto , a linha que contm a palavra reservada while . Um esqueleto da estrutura de repetio do-while mostrado a seguir, onde a seqncia de comandos composta por quaisquer comandos da linguagem C ou qualquer outra estrutura de programao:
Observe que, assim como em qualquer outra estrutura de programao, o bloco de comandos da estrutura de repetio do-while pode conter um nico comando e, por isso, os smbolos de delimitao de um bloco { e } so dispensveis neste caso. Note ainda que o teste de continuidade desta estrutura de repetio realizado sempre no nal, aps todos os comandos de seu bloco interno de comandos terem sido executados. Isso signica que esse bloco de comandos ser executado ao menos uma vez. Neste sentido, esta estrutura de repetio do-while difere bastante das estruturas de repetio while e for que vimos anteriormente, j que nesses dois ltimos casos o resultado da avaliao da expresso condicional associada a essas estruturas pode fazer com que seus blocos de comandos respectivos no sejam executados nem mesmo uma nica vez.
Exerccios
13.1 Dado um nmero inteiro no-negativo n, escreva um programa que determine quantos dgitos o nmero n possui. 13.2 Solucione o exerccio 12.3 usando a estrutura de repetio do-while . 13.3 Dizemos que um nmero natural n com pelo menos 2 algarismos palndromo se o primeiro algarismo de n igual ao seu ltimo algarismo; o segundo algarismo de n igual ao se penltimo algarismo; e assim sucessivamente. Exemplos:
DCT UFMS
13.1 D EFINIO
E EXEMPLO DE USO
78
#include <stdio.h> int main(void) { int n, digitos; printf("Informe n: "); scanf("%d", &n); digitos = 0; do { n = n / 10; digitos++; } while (n > 0); printf("O nmero tem %d dgitos\n", digitos); return 0; }
567765 palndromo; 32423 palndromo; 567675 no palndromo. Dado um nmero natural n, n 10, vericar se n palndromo.
13.4 Dados um nmero inteiro n > 0 e uma seqncia de n nmeros inteiros, determinar quantos segmentos de nmeros iguais consecutivos compem essa seqncia. Exemplo: Para n = 9, a seqncia 5 , 2, 2, 4, 4, 4, 4, 1, 1 formada por 4 segmentos de nmeros iguais. 13.5 Dados um nmero inteiro n > 0 e uma seqncia de n nmeros inteiros, determinar o comprimento de um segmento crescente de comprimento mximo. Exemplos: Na seqncia 5, 10, 6, 2, 4, 7, 9 , 8, 3 o comprimento do segmento crescente mximo 4. Na seqncia 10, 8, 7, 5, 2 o comprimento do segmento crescente mximo 1.
DCT
UFMS
A ULA 14
N MEROS
Dadas as diculdades inerentes da representao de nmeros reais nos computadores, as linguagens de programao de alto nvel procuram super-las abstraindo essa representao atravs do uso de nmeros com ponto utuante. Na linguagem C, nmeros com ponto utuante podem ser manipulados como constantes ou variveis do tipo ponto utuante. O armazenamento destes nmeros em memria se d de forma distinta do armazenamento de nmeros inteiros e necessita de mais espao, como poderamos supor. Nesta aula veremos como manipular nmeros de ponto utuante, formalizando as denies de constantes e variveis envolvidas, alm de aprender as regras de uso desses elementos em expresses aritmticas.
79
14.1 C ONSTANTES
80
A distino entre os tipos de ponto utuante float e double se d pela quantidade de preciso necessria. Quando a preciso no crtica, o tipo float adequado. O tipo double fornece preciso maior. A tabela a seguir mostra as caractersticas dos tipos de ponto utuante quando implementados de acordo com o padro IEEE1 . Em computadores que no seguem o padro IEEE, esta tabela pode no ser vlida. Tipo float double Menor valor (positivo) 1.17549 1038 2.22507 10308 Maior valor 3.40282 1038 1.79769 10308 Preciso 6 dgitos 15 dgitos
O padro IEEE 754 estabelece dois formatos primrios para nmeros de ponto utuante: o formato de preciso simples, com 32 bits, e o formato de preciso dupla, com 64 bits. Os nmeros de ponto utuante so armazenados no formato de notao cientca, composto por trs partes: um sinal, um expoente e uma frao. O nmero de bits reservado para representar o expoente determina quo grande o nmero pode ser, enquanto que o nmero de bits da frao determina sua preciso. Um exemplo de um programa que usa constantes e variveis do tipo float apresentado no programa 14.1. Neste programa, uma seqncia de cem nmeros do tipo ponto utuante informada pelo usurio. Em seguida, a mdia aritmtica desses nmeros calculada e, por m, mostrada na sada padro. Programa 14.1: Calcula a mdia de 100 nmeros do tipo ponto utuante.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
#include <stdio.h> int main(void) { int i; float numero, soma, media; soma = 0.0; for (i = 1; i <= 100; i++) { printf("Informe um nmero: "); scanf("%f", &numero); soma = soma + numero; } media = soma / 100; printf("A mdia dos 100 nmeros %f\n", media); return 0; }
Observe que a cadeia de caracteres de formatao contm um especicador de converso para um nmero de ponto utuante %f , diferentemente do especicador de converso para nmeros inteiros ( %d ) que vimos anteriormente.
1
Instituto de Engenheiros Eltricos e Eletrnicos, do ingls Institute of Electrical and Eletronics Engineers (IEEE).
DCT
UFMS
14.2 E XPRESSES
ARITMTICAS
81
#include <stdio.h> int main(void) { int i1, i2; float f1, f2; i1 = 190; f1 = 100.5; i2 = i1 / 100; printf("i2 = %d\n", f2 = i1 / 100; printf("f2 = %f\n", f2 = i1 / 100.0; printf("f2 = %f\n", f2 = f1 / 100; printf("f2 = %f\n", return 0; }
i2 f2 f2 f2
= = = =
O primeiro valor um nmero do tipo inteiro que representa o quociente da diviso de dois nmeros do tipo inteiro 190 e 100 . J tnhamos trabalhado com expresses aritmticas semelhantes em aulas anteriores. No segundo valor temos de olhar para a expresso aritmtica direita do comando de atribuio. Esta expresso uma expresso aritmtica que s contm operandos do tipo inteiro. Por isso, o resultado o nmero do tipo inteiro 1 , que atribudo varivel f2 . O comando printf mostra o contedo da varivel f2 como um nmero do tipo ponto utuante e assim a sada f2 = 1.000000 . Em seguida, nas prximas duas impresses, as expresses aritmticas correspondentes contm operandos do tipo ponto
DCT UFMS
14.2 E XPRESSES
ARITMTICAS
82
utuante: a constante do tipo ponto utuante 100.0 na primeira e a varivel do tipo ponto utuante f1 na segunda. Por isso, as expresses aritmticas tm como resultados nmeros do tipo ponto utuante e a sada mostra os resultados esperados. Suponha, no entanto, que um programa semelhante, o programa 14.3, tenha sido digitado, salvo, compilado e executado. Programa 14.3: Outro programa que apresenta valores do tipo inteiro e de ponto utuante.
1 2 3 4 5 6 7 8 9 10 11
#include <stdio.h> int main(void) { int i1, i2; float f1; i1 = 3; i2 = 2; f1 = i1 / i2; printf("f1 = %f\n", f1); return 0; }
f1 = 1.000000
Mas e se quisssemos que a diviso i1/ i2 tenha como resultado um valor do tipo ponto utuante? Neste caso, devemos usar um operador unrio chamado operador conversor de tipo, do ingls type cast operator, sobre alguma das variveis da expresso. No caso acima, poderamos usar qualquer uma das atribuies abaixo:
f1 = 1.500000
14.2 E XPRESSES
ARITMTICAS
83
Um outro operador unrio, que faz o inverso do operador conversor do tipo ponto utuante, o operador conversor do tipo inteiro, denotado por (int) . Esse operador conversor do tipo inteiro converte o valor de uma expresso aritmtica para um valor do tipo inteiro. Vejamos o programa 14.4. Programa 14.4: Mais um programa que apresenta valores do tipo inteiro e de ponto utuante.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
#include <stdio.h> int main(void) { int i1, i2, i3, i4; float f1, f2; f1 = 10.8; f2 = 1.5; i1 = f1 / f2; printf("i1 = %d\n", i1); i2 = (int) f1 / f2; printf("i2 = %d\n", i2); i3 = f1 / (int) f2; printf("i3 = %d\n", i3); i4 = (int) f1 / (int) f2; printf("i4 = %d\n", i4); return 0; }
i1 i2 i3 i4
= = = =
7 6 10 10
As primeiras duas atribuies fazem com que as variveis f1 e f2 recebam as constantes de ponto utuante 10.8 e 1.5 . Em seguida, a atribuio i1 = f1 / f2; faz com que o resultado da expresso aritmtica direita do comando de atribuio seja atribudo varivel i1 do tipo inteiro. A expresso aritmtica devolve um valor de ponto utuante: 10.8 / 1.5 = 7.2 . Porm, como do lado esquerdo da atribuio temos uma varivel do tipo inteiro, a parte fracionria deste resultado descartada e o valor 7 ento armazenado na varivel i1 e mostrado com o comando printf . Na prxima linha, a expresso aritmtica do lado direito da atribuio (int) f1 / f2 que avaliada como (int) 10.8 / 1.5 = 10 / 1.5 = 6.666666 e, como do lado direito do comando de atribuio temos uma varivel do tipo inteiro i2 , o valor 6 armazenado nesta varivel e mostrado na sada atravs do comando printf na linha seguinte. Na prxima linha, a expresso aritmtica f1 / (int) f2 avaliada como 10.8 / (int) 1.5 = 10.8 / 1 = 10.8 e, de novo, como do lado direito do
DCT UFMS
14.2 E XPRESSES
ARITMTICAS
84
comando de atribuio temos uma varivel do tipo inteiro i3 , o valor armazenado nesta varivel 10 , que tambm mostrado na sada atravs do comando printf logo a seguir. E, por m, a expresso aritmtica seguinte (int) f1 / (int) f2 e sua avaliao dada por (int) 10.8 / (int) 1.5 = 10 / 1 = 10 e portanto este valor atribudo varivel i4 e apresentado na sada pelo comando printf . Um compilador da linguagem C considera qualquer constante com ponto utuante como sendo uma constante do tipo double , a menos que o programador explicitamente diga o contrrio, colocando o smbolo f no nal da constante. Por exemplo, a constante 3.1415f uma constante com ponto utuante do tipo float , ao contrrio da constante 55.726 , que do tipo double . Uma constante de ponto utuante tambm pode ser expressa em notao cientca. O valor
1.342e-3 um valor de ponto utuante e representa o valor 1,342 103 ou 0,001324. O valor antes do smbolo e chamado mantissa e o valor aps esse smbolo chamado expoente do
nmero de ponto utuante. Para mostrar um nmero com ponto utuante do tipo double na sada padro podemos usar a mesmo caracter de converso de tipo %f que usamos para mostrar um nmero com ponto utuante do tipo float . Para realizar a leitura de um nmero de ponto utuante que ser armazenado em uma varivel do tipo double usamos os caracteres %lf como caracteres de converso de tipo. Um outro exemplo do uso de constantes e variveis do tipo ponto utuante ( float e double ) mostrado no programa 14.5, que computa a rea de um crculo cujo valor do raio informado pelo usurio em radianos. Programa 14.5: Programa para clculo da rea do crculo.
1 2 3 4 5 6 7 8 9 10 11 12
#include <stdio.h> int main(void) { float pi; double raio, area; pi = 3.141592f; printf("Digite o valor do raio: "); scanf("%lf", &raio); area = (double)pi * raio * raio; printf("A rea do crculo %f\n", area); return 0; }
Exerccios
14.1 Uma pessoa aplicou um capital de x reais a juros mensais de y % durante 1 ano. Determinar o montante de cada ms durante este perodo.
DCT UFMS
14.2 E XPRESSES
ARITMTICAS
85
#include <stdio.h> int main(void) { int mes; float x, y; printf("Informe o capital inicial: "); scanf("%d", &x); printf("Informe a taxa de juros: "); scanf("%d", &y); for (mes = 1; mes <= 12; mes++) { x = x * (1 + y / 100); printf("Ms: %d Montante: %f\n", mes, x); } return 0; }
14.2 Os pontos (x, y ) que pertencem gura H (veja a gura 14.1) so tais que x 0, y 0 e x2 + y 2 1. Dados n pontos reais (x, y ), verique se cada ponto pertence ou no a H .
y
1111111111 0000000000 0000000000 1111111111 0000000000 1111111111 0000000000 1111111111 0000000000 1111111111 0000000000 1111111111 H 0000000000 1111111111 0000000000 1111111111 0000000000 1111111111 0000000000 1111111111 x
Figura 14.1: rea H de um quarto de um crculo. 14.3 Considere o conjunto H = H1 H2 de pontos reais, onde H1 = {(x, y )|x 0, y H2 = {(x, y )|x 0, y + x2 2x 3 0, y + x2 + 2x 3 0} 0}
Dado um nmero inteiro n > 0, leia uma seqncia de n pontos reais (x, y ) e verique se cada ponto pertence ou no ao conjunto H , contando o nmero de pontos da seqncia que pertencem a H . 14.4 Dado um natural n, determine o nmero harmnico Hn denido por
n
Hn =
k =1
1 . k
DCT
UFMS
14.2 E XPRESSES
ARITMTICAS
86
14.5 Para n > 0 alunos de uma determinada turma so dadas 3 notas de provas. Calcular a mdia aritmtica das provas de cada aluno, a mdia da classe, o nmero de aprovados e o nmero de reprovados, onde o critrio de aprovao mdia 5.0. 14.6 Dados nmeros reais a, b e c, calcular as razes de uma equao do 2o grau da forma ax2 + bx + c = 0. Imprimir a soluo em uma das seguintes formas: a. DUPLA raiz b. REAIS DISTINTAS raiz 1 raiz 2 c. COMPLEXAS parte real parte imaginria
DCT
UFMS
A ULA 15
E XERCCIOS
Nesta aula faremos mais exerccios usando nmeros com ponto utuante.
Enunciados
15.1 Dado um inteiro positivo n, calcular e imprimir o valor da seguinte soma 2 3 n 1 + + + ... + . n n1 n2 1 Programa 15.1: Uma soluo para o exerccio 15.1.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
#include <stdio.h> int main(void) { int n, numerador, denominador; float soma; printf("Informe o valor de n: "); scanf("%d", &n); numerador = 1; denomidador = n; soma = 0.0; for (i = 1; i <= n; i++) { soma = soma + (float) numerador / denominador; numerador++; denominador--; } printf("Soma = %f", soma); return 0; }
15.2 Faa um programa que calcula a soma 1 pelas seguintes maneiras: 87 1 1 1 1 1 + + ... + 2 3 4 9999 10000
88 (a) adio dos termos da direita para a esquerda; (b) adio dos termos da esquerda para a direita; (c) adio separada dos termos positivos e dos termos negativos da esquerda para a direita; (d) adio separada dos termos positivos e dos termos negativos da direita para a esquerda; (e) frmula de recorrncia; (f) frmula do termo geral. 15.3 Uma maneira de calcular o valor do nmero utilizar a seguinte srie: =4 4 4 4 4 4 + + + ... 3 5 7 9 11
Fazer um programa para calcular e imprimir o valor de atravs da srie acima, com preciso de 4 casas decimais. Para obter a preciso desejada, adicionar apenas os termos cujo valor absoluto seja maior ou igual a 0,0001. 15.4 Dado um nmero real x tal que 0 x 1, calcular uma aproximao do arco tangente de x em radianos atravs da srie innita: arctan x = x x3 x5 x7 + + ... 3 5 7 0.
2k+1
Dados dois nmeros reais x e , calcular o valor de ex incluindo todos os termos Tk = x k! , com k 0, at que Tk < . Mostre na sada o valor computado e o nmero total de termos usados na srie. 15.6 Dados x real e n natural, calcular uma aproximao para cos x, onde x dado em radianos, atravs dos n primeiros termos da seguinte srie: cos x = 1 com k x2k x2 x4 x6 + + . . . + (1)k + ... 2! 4! 6! (2k)!
15.7 Dados x e reais, > 0, calcular uma aproximao para sen x, onde x dado em radianos, atravs da seguinte srie innita
sen x =
com k
DCT
UFMS
A ULA 16
E XERCCIOS
Enunciados
16.1 Dados um nmero inteiro n e n seqncias de nmeros inteiros, cada qual terminada por 0, determinar a soma dos nmeros pares de cada seqncia. Programa 16.1: Uma soluo para o exerccio 16.1.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
#include <stdio.n> int main(void) { int n, num, soma; printf("Informe o valor de n: "); scanf("%d", &n); for (i = 1; i <= n; i++) { soma = 0; do { printf("(Sequncia %d) Informe um nmero: ", i); scanf("%d", &num); if (!(num % 2)) soma = soma + num; } while (num != 0); printf("(Sequncia %d) Soma = %d\n", i, soma); } return 0; }
16.2 Dados um nmero inteiro n > 0 e uma seqncia de n nmeros inteiros positivos determinar o fatorial de cada nmero da seqncia. 16.3 Dados n nmeros inteiros positivos, calcular a soma dos que so primos. 16.4 Dado um nmero inteiro positivo n, determinar todos os inteiros entre 1 e n que so comprimento de hipotenusa de um tringulo retngulo com catetos inteiros. 89
90 16.5 Dados dois naturais m e n, determinar, entre todos os pares de nmeros naturais (x, y ) tais que x m e y n, um par para o qual o valor da expresso xy x2 + y seja mximo e calcular tambm esse mximo. 16.6 Sabe-se que um nmero da forma n3 igual soma de n nmeros mpares consecutivos. Exemplo: 13 23 33 43 . . . =1 =3+5 = 7 + 9 + 11 = 13 + 15 + 17 + 19
Dado m, determine os mpares consecutivos cuja soma igual a n3 para n assumindo valores de 1 a m. 16.7 Dado um nmero inteiro positivo, determine a sua decomposio em fatores primos, calculando tambm a multiplicidade de cada fator. Exemplo: Se n = 600 a sada deve ser fator 2 multiplicidade 3 fator 3 multiplicidade 1 fator 5 multiplicidade 2 16.8 Dados n inteiros positivos, determinar o mximo divisor comum entre eles.
DCT
UFMS
A ULA 17
C ARACTERES
Nesta aula vamos estudar um novo tipo bsico de dados, o tipo caracter. Na linguagem C existem dois tipos de caracteres: caracter com sinal e caracter sem sinal. Esses dois tipos so apenas interpretaes diferentes do conjunto de todas as seqncias de 8 bits, sendo que essa diferena irrelevante na prtica. Esta aula baseada especialmente no livro [4] e tambm no livro [7].
17.1 R EPRESENTAO
GRFICA
92
Na tabela a seguir, apresentamos alguns desses caracteres no-imprimveis. Muitos deles no so mostrados por opo ou por terem cado em desuso. Bin 0000 0000 0000 0111 0000 1001 0000 1010 0000 1011 0000 1100 0000 1101 0111 1111 Dec 00 07 09 10 11 12 13 127 Signicado Nulo Campainha Tabulao horizontal Mudana de linha Tabulao vertical Quebra de pgina Retorno do carro/cursor Delete
A tabela a seguir mostra os 95 caracteres imprimveis da tabela ASCII. Bin 0010 0000 0010 0001 0010 0010 0010 0011 0010 0100 0010 0101 0010 0110 0010 0111 0010 1000 0010 1001 0010 1010 0010 1011 0010 1100 0010 1101 0010 1110 0010 1111 0011 0000 0011 0001 0011 0010 0011 0011 0011 0100 0011 0101 0011 0110 0011 0111 0011 1000 0011 1001 0011 1010 0011 1011 0011 1100 0011 1101 0011 1110 0011 1111
DCT
Dec 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
Bin 0100 0000 0100 0001 0100 0010 0100 0011 0100 0100 0100 0101 0100 0110 0100 0111 0100 1000 0100 1001 0100 1010 0100 1011 0100 1100 0100 1101 0100 1110 0100 1111 0101 0000 0101 0001 0101 0010 0101 0011 0101 0100 0101 0101 0101 0110 0101 0111 0101 1000 0101 1001 0101 1010 0101 1011 0101 1100 0101 1101 0101 1110 0101 1111
Dec 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
Sim @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _
Bin 0110 0000 0110 0001 0110 0010 0110 0011 0110 0100 0110 0101 0110 0110 0110 0111 0110 1000 0110 1001 0110 1010 0110 1011 0110 1100 0110 1101 0110 1110 0110 1111 0111 0000 0111 0001 0111 0010 0111 0011 0111 0100 0111 0101 0111 0110 0111 0111 0111 1000 0111 1001 0111 1010 0111 1011 0111 1100 0111 1101 0111 1110
Dec 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
Sim a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~
UFMS
17.2 C ONSTANTES
E VARIVEIS
93
Os caracteres com sinal, quando dispostos em ordem crescente, apresentam-se de tal forma que as letras acentuadas precedem as no-acentuadas. Os caracteres sem sinal, ao contrrio, em ordem crescente apresentam-se de forma que as letras no-acentuadas precedem as acentuadas. Essa a nica diferena entre caracteres com e sem sinal.
so declaraes vlidas de variveis do tipo caracter. Uma constante do tipo caracter um nmero no intervalo de 0 a 255 ou de 128 a 127. Por exemplo, para as variveis c , d e e declarada acima, podemos fazer
Mais comum e confortavelmente, podemos especicar uma constante do tipo caracter atravs da representao grca de um caracter, envolvendo-o por aspas simples. Por exemplo, z , ; e 3 so exemplos de constantes do tipo caracter. Assim, bem mais cmodo fazer as atribuies
c = z; d = ;; e = 3;
que, na prtica, so idnticas s anteriores. Alguns caracteres produzem efeitos especiais tais como acionar um som de campainha ou realizar uma tabulao horizontal. Para representar um caracter como esse na linguagem C usamos uma seqncia de dois caracteres consecutivos iniciada por uma barra invertida. Por exemplo, \n o mesmo que 10 e representa uma mudana de linha. A tabela a seguir mostra algumas constantes do tipo caracter.
DCT UFMS
17.2 C ONSTANTES
E VARIVEIS
94 constante \0 \t \n \v \f \r 7 \\ a smbolo grco caracter nulo tabulao horizontal mudana de linha tabulao vertical quebra de pgina retorno do carro/cursor espao 7 \ a
caracter 0 9 10 11 12 13 32 55 92 97
Na linguagem C, um branco (do ingls whitespace) denido como sendo um caracter que uma tabulao horizontal, uma mudana de linha, uma tabulao vertical, uma quebra de pgina, um retorno de carro/cursor e um espao. Ou seja, os caracteres 9, 10, 11, 12, 13 e 32 so brancos e as constantes correspondentes so \t , \n , \v , \f , \r e . A funo scanf trata todos os brancos como se fossem , assim como outras funes da linguagem C tambm o fazem. Para imprimir um caracter na sada padro com a funo printf devemos usar o especicador de converso de tipo caracter %c na seqncia de caracteres de formatao. Para ler um caracter a partir da entrada padro com a funo scanf tambm devemos usar o mesmo especicador de converso de tipo caracter %c na seqncia de caracteres de formatao. Um exemplo de uso do tipo bsico caracter mostrado no programa 17.1. Programa 17.1: Um programa usando o tipo char .
1 2 3 4 5 6 7 8 9 10 11 12 13
#include <stdio.h> int main(void) { char c; c = a; printf("%c\n", c); c = 97; printf("%c\n", c); printf("Informe um caracter: "); scanf("%c", &c); printf("%c = %d\n", c, c); return 0; }
Como vimos at aqui nesta seo, por enquanto no h muita utilidade para constantes e variveis do tipo caracter. Mas em breve iremos us-las para construir um tipo de dados muito importante chamado de cadeia de caracteres.
DCT
UFMS
17.3 E XPRESSES
COM CARACTERES
95
Na ltima linha o resultado da expresso x + y 305 . No entanto, atribuies de nmeros inteiros a variveis do tipo caracter so feitas mdulo 256. Por isso, a varivel z acima receber o valor 49 . Do mesmo modo, aps a execuo do trecho de cdigo a seguir:
a varivel x conter o valor 0 e a varivel c conter o valor -120 . Expresses relacionais podem naturalmente envolver caracteres. No trecho de cdigo a seguir, algumas expresses lgicas envolvendo caracteres so mostradas:
char c; c = a; if (a <= c && c <= z) c = c - ; printf("%c = %d\n", c, c); if (65 <= c && c <= 122) c = c + a; printf("%c = %d\n", c, c);
Qual a sada gerada pelas duas chamadas da funo printf no trecho de cdigo acima?
DCT
UFMS
17.3 E XPRESSES
COM CARACTERES
96
Exerccios
17.1 Verique se o programa 17.2 est correto. Programa 17.2: O que faz este programa?
1 2 3 4 5 6 7 8 9
#include <stdio.h> int main(void) { char c; for (c = 0; c < 128; c++) printf("."); printf("\ntchau!\n"); return 0; }
17.2 Escreva um programa que imprima todas as letras minsculas e todas as letras maisculas do alfabeto. 17.3 Escreva um programa que traduz um nmero de telefone alfabtico de 8 dgitos em um nmero de telefone na forma numrica. Suponha que a entrada sempre dada em caracteres maisculos. Exemplo: Se a entrada URGENCIA a sada deve ser 87436242 . 1111FOGO a sada deve ser 11113646 . Se a entrada
Se voc no possui um telefone, ento as letras que correspondem s teclas so as seguintes: 2=ABC, 3=DEF, 4=GHI, 5=JKL, 6=MNO, 7=PQRS, 8=TUV e 9=WXYZ. 17.4 Scrabble um jogo de palavras em que os jogadores formam palavras usando pequenos quadrados que contm uma letra e um valor. O valor varia de uma letra para outra, baseado na sua raridade. Os valores so os seguintes: AEILNORSTU=1, DG=2, BCMP=3, FHVWY=4, K=5, JX=8 e QZ=10. Escreva um programa que receba uma palavra e compute o seu valor, somando os valores de suas letras. Seu programa no deve fazer distino entre letras maisculas e minsculas. Exemplo: Se a palavra de entrada programa a sada tem de ser 13 . 17.5 Escreva um programa que receba dois nmeros inteiros a e b e um caracter op, tal que op pode ser um dos cinco operadores aritmticos disponveis na linguagem C ( + , - , * , / , % ), realize a operao a op b e mostre o resultado na sada.
DCT
UFMS
A ULA 18
T IPOS
DE DADOS BSICOS
Como vimos at esta aula, a linguagem C suporta fundamentalmente dois tipos de dados numricos: tipos inteiros e tipos com ponto utuante. Valores do tipo inteiro so nmeros inteiros em um dado intervalo nito e valores do tipo ponto utuante so tambm dados em um intervalo nito e podem ter uma parte fracionria. Alm desses, o tipo caracter tambm suportado pela linguagem e um valor do tipo caracter tambm um nmero inteiro em um intervalo nito mais restrito que o dos tipos inteiros. Nesta aula faremos uma reviso dos tipos de dados bsicos da linguagem C e veremos os especicadores desses tipos, seus especicadores de converso para entrada e sada de dados, as converses entre valores de tipos distintos, alm de denir nossos prprios tipos de dados. Esta aula uma reviso das aulas 7, 14 e 17 e tambm uma extenso desses conceitos, podendo ser usada como uma referncia para tipos de dados bsicos. As referncias usadas nesta aula so [4, 7].
97
18.1 T IPOS
INTEIROS
98
short int unsigned short int int unsigned int long int unsigned long int
Qualquer outra combinao de especicadores na declarao de uma varivel equivalente a uma das combinaes acima. A ordem que os especicadores ocorrem no tem inuncia sobre o resultado nal: declaraes feitas com unsigned short int ou com short unsigned int tm o mesmo resultado. Quando no estritamente necessrio, a linguagem C permite que a palavra reservada int seja omitida, como por exemplo em unsigned short e long . O intervalo de valores representado por cada um dos seis tipos inteiros varia de uma mquina para outra. Os compiladores, entretanto, devem obedecer algumas regras fundamentais. Em especial, o padro especica que o tipo int no seja menor que o tipo short int e que long int no seja menor que int . As mquinas atuais, em sua grande maioria, so de 32 bits e a tabela abaixo mostra os intervalos de cada um dos possveis tipos inteiros. Observe que os tipos int e long int tm intervalos idnticos nas mquinas de 32 bits. Em mquinas de 64 bits, h diferenciao entre esses dois tipos. Tipo short int unsigned short int int unsigned int long int unsigned long int Menor valor 32.768 0 2.147.483.648 0 2.147.483.648 0 Maior valor 32.767 65.535 2.147.483.647 4.294.967.294 2.147.483.647 4.294.967.294
Constantes numricas, como vimos, so nmeros que ocorrem no cdigo dos programas, em especial em expresses aritmticas. As constantes, assim como as variveis, podem ser nmeros inteiros ou nmeros com ponto utuante. Na linguagem C, as constantes numricas do tipo inteiro podem ser descritas em decimal ou base 10, octal ou base 8 ou ainda hexadecimal ou base 16. Constantes decimais contm dgitos de 0 (zero) a 9 (nove), mas no devem iniciar com 0 (zero). Exemplos de constantes decimais so mostrados a seguir:
75
-264
32767
Constantes octais contm somente dgitos entre 0 (zero) e 7 (sete) e devem comear necessariamente com o dgito 0 (zero). Exemplos de constantes octais so mostrados a seguir:
DCT UFMS
18.1 T IPOS
INTEIROS
99
075
0367
07777
E constante hexadecimais contm dgitos entre 0 (zero) e 9 (nove) e letras entre a e f, e sempre devem iniciar com os caracteres 0x. As letras podem ser maisculas ou minsculas. Exemplos de constantes hexadecimais so mostrados a seguir:
0xf
0x8aff
0X12Acf
importante destacar que a descrio de constantes como nmeros na base octal e hexadecimal apenas uma forma alternativa de escrever nmeros que no tem efeito na forma como so armazenados na memria, j que sempre so armazenados na base binria. Alm disso, essas notaes so mais usadas quando trabalhamos com programas de baixo nvel. No h necessidade de se convencionar uma nica notao para escrever constantes e podemos trocar de uma notao para outra a qualquer momento. Na verdade, podemos inclusive misturar essas notaes em expresses aritmticas como a seguir:
e o resultado dessa expresso 420 na base decimal. O tipo de uma constante que um nmero inteiro na base decimal normalmente int . Se o valor da constante for muito grande para armazen-la como um int , a constante ser armazenada como um long int . Se mesmo nesse caso ainda no for possvel armazenar tal constante, o compilador tenta, como um ltimo recurso, armazenar o valor como um unsigned long int . Constantes na base octal ou hexadecimal so armazenadas de maneira semelhante, com o compilador tentando armazen-la progressivamente como um int , unsigned int , long int ou unsigned long int . Para forar que o compilador trate uma constante como um nmero inteiro grande, devemos adicionar a letra L ou l ao nal da constante. Como exemplo, as constantes abaixo so do tipo long int :
22L
055l
0xffffL
Para indicar que uma constante no tem sinal, devemos adicionar a letra U ou u ao nal da constante. Por exemplo, as constantes abaixo so do tipo unsigned int :
DCT
UFMS
18.1 T IPOS
INTEIROS
100
22u
072U
0xa09DU
Podemos indicar que uma constante um nmero inteiro grande e sem sinal usando uma combinao das letras acima, em qualquer ordem. Por exemplo, as constantes abaixo so do tipo unsigned long int :
22ul
07777UL
0xFFFFAALU
A funo printf usada para imprimir, entre outros, nmeros inteiros. Relembrando, a funo printf tem o seguinte formato:
printf(cadeia, expresso 1, expresso 2, . . .);
onde cadeia a cadeia de caracteres de formatao. Quando a funo printf chamada, a impresso da cadeia de caracteres de formatao ocorre na sada. Essa cadeia pode conter caracteres usuais, que sero impressos diretamente, e tambm especicaes de converso, cada uma iniciando com o caracter % e representando um valor a ser preenchido durante a impresso. A informao que sucede o caracter % especica como o valor deve ser convertido a partir de sua representao interna binria para uma representao imprimvel. Caracteres usuais na cadeia de caracteres de formatao so impressos na sada exatamente como ocorrem na cadeia enquanto que as especicaes de converso so trocadas por valores a serem impressos. A tabela abaixo mostra tipos e especicadores de tipos e tambm especicadores de converso para cadeias de caracteres de formatao da funo printf , especicamente para tratamento de nmeros inteiros. Especicador e tipo short int unsigned short int int unsigned int long int unsigned long int Especicador de converso %hd ou %hi %hu %d ou %i %u %ld ou %li %lu
A forma geral de um especicador de converso para nmeros inteiros em uma cadeia de caracteres de formatao da funo printf a seguinte:
%[flags][comprimento][.preciso][hl]conversor
Os campos opcionais so mostrados entre colchetes. Ou seja, apenas conversor obrigatrio, que chamamos de especicador de converso. A tabela a seguir mostra os possveis ags para um especicador de converso de uma cadeia de caracteres de formatao da funo printf .
DCT UFMS
18.1 T IPOS
INTEIROS
101 Signicado Valor alinhado esquerda Valor precedido por + ou Valor positivo precedido por espaos Nmero preenchido com zeros esquerda
Flag + espaos 0
Os especicadores de converso o e x (ou X ) permitem ainda que um nmero inteiro a ser mostrado na sada o seja na base octal ou hexadecimal, respectivamente. Se o nmero inteiro pequeno, grande, com ou sem sinal, essas opes tambm podem ser descritas no especicador. O programa 18.1 usa os tipos bsicos conhecidos para nmeros inteiros e seus especicadores, juntamente com caracteres especicadores de converso nas cadeias de caracteres de formatao das chamadas da funo printf . Programa 18.1: Exemplo de tipos e especicadores de tipos inteiros e tambm de formatao de impresso com especicadores de converso.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
#include <stdio.h> int main(void) { short int i1; unsigned short int i2; int i3; unsigned int i4; long int i5; unsigned long int i6; i1 = 159; i2 = 630u; i3 = -991023; i4 = 98979U; i5 = 8393202L; i6 = 34268298UL; printf("%hd %d %ho\n", i1, i1, i1); printf("%hu %i %hx\n", i2, i2, i2); printf("%+d % d %09d\n", i3, i3, i3); printf("%X %d %u\n", i4, i4, i4); printf("%+ld %ld %10ld\n", i5, i5, i5); printf("%lu %10.8lu %-lu\n", i6, i6, i6); return 0; }
Assim como para a funo printf , outras opes de formatao tambm esto disponveis para a funo scanf , mais que aquelas vistas at esta aula. Neste caso tambm vale o conceito de especicadores de converso. Quando uma funo scanf executada, especicadores de converso so procurados na cadeia de caracteres de formatao (de leitura), aps o smbolo usual % .
DCT UFMS
18.1 T IPOS
INTEIROS
102
Os especicadores de converso h e l podem ser usados em uma cadeia de caracteres de formatao de leitura, na funo scanf , com o mesmo sentido que na cadeia de caracteres de formatao de escrita, na funo printf , isto , para indicar que a leitura ser feita como um nmero inteiro curto ou longo, respectivamente. Um comprimento tambm pode ser usado, indicando o comprimento mximo do valor a ser armazenado na varivel correspondente.
Especicador d Signicado valor a ser lido expresso em notao decimal; o argumento correspondente do tipo int, a menos que os modicadores de converso h ou l tenham sido usados, casos em que o valor ser short int ou long int, respectivamente como %d, exceto nmeros na base octal (comeando com 0) ou hexadecimal (comeando com 0x ou 0X,) que tambm podem ser lidos valor a ser lido um inteiro e o argumento correspondente do tipo unsigned int valor a ser lido expresso na notao octal e pode opcionalmente ser precedido por 0; o argumento correspondente um int, a menos que os modicadores de converso h ou l tenham sido usados, casos em que short int ou long int, respectivamente valor a ser lido expresso na notao hexadecimal e pode opcionalmente ser precedido por 0x ou 0X; o argumento correspondente um unsigned int, a menos que os modicadores de converso h ou l tenham sido usados, casos em que short int ou long int, respectivamente
u o
A funo scanf executada primeiro esperando por um valor a ser informado pelo usurio e, depois, formatando este valor atravs do uso da cadeia de caracteres de formatao e do especicador de converso. A funo scanf naliza a leitura de dados sempre que o usurio informa um branco, que o caracter 32 (espao ou ), o caracter 9 (tabulao horizontal ou \t ), o caracter 10 (tabulao vertical ou \v ), o caracter 11 (retorno de carro/cursor ou \r ), o caracter 12 (mudana de linha ou \n ) ou o caracter 13 (avano de pgina ou \f ). Nesse sentido, considere uma entrada como no trecho de cdigo abaixo:
prompt$ ./a.out 4 7
ou
DCT
UFMS
18.2 N MEROS
103
prompt$ ./a.out 4 7
ou ainda
prompt$ ./a.out 4
o resultado ser o mesmo, ou seja, os valores 4 e 7 sero armazenados nas variveis a e b , respectivamente.
Ponto utuante de preciso simples Ponto utuante de preciso dupla Ponto utuante de preciso estendida
O tipo float usado quando a necessidade de preciso no crtica. O tipo double fornece maior preciso, suciente para a maioria dos problemas. E o tipo long double fornece a maior preciso possvel e raramente usado. A linguagem C no estabelece padro para a preciso dos tipos com ponto utuante, j que diferentes computadores podem armazenar nmeros com ponto utuante de formas diferentes. Os computadores mais recentes seguem, em geral, as especicaes do Padro 754 da IEEE. A tabela abaixo, mostrada na aula 14, mostra as caractersticas dos tipos numricos com ponto utuante quando implementados sob esse padro. Tipo float double Menor valor (positivo) 1.17549 1038 2.22507 10308 Maior valor 3.40282 1038 1.79769 10308 Preciso 6 dgitos 15 dgitos
Constantes com ponto utuante podem ser descritas de diversas formas. Por exemplo, as constantes abaixo so formas vlidas de escrever o nmero 391,0:
391.0
391.
391.0e0
391E0
3.91e+2
.391e3
3910.e-1
DCT
UFMS
18.3 C ARACTERES
104
Uma constante com ponto utuante deve conter um ponto decimal e/ou um expoente, que uma potncia de 10 pela qual o nmero multiplicado. Se um expoente descrito, ele deve vir precedido da letra e ou E . Opcionalmente, um sinal + ou - pode aparecer logo aps a letra e ou E . Por padro, constantes com ponto utuante so armazenadas como nmeros de ponto utuante de preciso dupla. Isso signica que, quando um compilador C encontra em um programa uma constante 391.0 , por exemplo, ele armazena esse nmero na memria no mesmo formato de uma varivel do tipo double . Se for necessrio, podemos forar o compilador a armazenar uma constante com ponto utuante no formato float ou long double . Para indicar preciso simples basta adicionar a letra f ou F ao nal da constante, como por exemplo 391.0f . Para indicar preciso estendida necessrio adicionar a letra l ou L ao nal da constante, como por exemplo 391.0L . Os especicadores de converso para nmeros com ponto utuante de preciso simples so
%e , %f e %g , tanto para escrita como para leitura. O formato geral de um especicador de
converso para nmeros com ponto utuante parecido com aquele descrito na seo anterior, como podemos observar abaixo:
%[flags][comprimento][.preciso][lL]conversor
O especicador de converso %e mostra/solicita um nmero com ponto utuante no formato exponencial ou notao cientca. A preciso indica quantos dgitos aps o ponto sero mostrados/solicitados ao usurio, onde o padro 6. O especicador de converso %f mostra/solicita um nmero com ponto utuante no formato com casas decimais xas, sem expoente. A preciso indica o mesmo que para %e . E o especicador de converso %g mostra o nmero com ponto utuante no formato exponencial ou com casas decimais xas, dependendo de seu tamanho. Diferentemente dos especicadores anteriores, a preciso neste caso indica o nmero mximo de dgitos signicativos a ser mostrado/solicitado. Ateno deve ser dispensada com pequenas diferenas na escrita e na leitura de nmeros com ponto utuante de preciso dupla e estendida. Quando da leitura de um valor do tipo double necessrio colocar a letra l precedendo e , f ou g . Esse procedimento necessrio apenas na leitura e no na escrita. Quando da leitura de um valor do tipo long double necessrio colocar a letra L precedendo e , f ou g .
18.3 Caracteres
Como mencionamos na aula 17, na linguagem C cada caracter armazenado em um nico byte na memria do computador. Assim, um caracter sem sinal um nmero do conjunto {0, . . . , 255} e um caracter com sinal um nmero do conjunto {128, . . . , 1, 0, 1, . . . , 127}. Ou seja, um caracter uma seqncia de 8 bits, dentre as 256 seqncias de 8 bits possveis. A impresso de um caracter na sada padro a sua representao como um smbolo grco. Por exemplo, o smbolo grco do caracter 97 a . Alguns caracteres tm representaes grcas especiais, como o caracter 10 que representado por uma mudana de linha. Uma varivel do tipo caracter (com sinal) pode ser declarada na linguagem C com a palavra reservada char . Uma varivel do tipo caracter sem sinal pode ser declarada com a mesma palavra reservada char , mas com o especicador de tipo unsigned a precedendo. Assim,
DCT UFMS
18.3 C ARACTERES
105
so declaraes vlidas de variveis do tipo caracter. Uma constante do tipo caracter sem sinal um nmero no intervalo de 0 a 255 e uma constante do tipo caractere com sinal uma constante no intervalo de 128 a 127. Por exemplo, para as variveis c , d e e declaradas acima, podemos fazer
Mais comum e confortavelmente, podemos especicar uma constante do tipo caracter atravs da representao grca de um caracter, envolvendo-o por aspas simples. Por exemplo, z , ; e 3 so exemplos de constantes do tipo caracter. Assim, bem mais cmodo fazer as atribuies
c = z; d = ;; e = 3;
que, na prtica, so idnticas. Alguns caracteres produzem efeitos especiais tais como acionar um som de campainha ou realizar uma tabulao horizontal. Para representar um caracter como esse na linguagem C usamos uma seqncia de dois caracteres consecutivos iniciada por uma barra invertida. Por exemplo, \n o mesmo que 10 e representa uma mudana de linha. A tabela a seguir mostra algumas constantes do tipo caracter. caracter 0 9 10 11 12 13 32 55 92 97
DCT
constante \0 \t \n \v \f \r 7 \\ a
smbolo grco caracter nulo tabulao horizontal mudana de linha tabulao vertical quebra de pgina retorno do carro/cursor espao 7 \ a
UFMS
18.3 C ARACTERES
106
Na linguagem C, um branco (do ingls whitespace) denido como sendo um caracter que uma tabulao horizontal, uma mudana de linha, uma tabulao vertical, uma quebra de pgina, um retorno de carro/cursor ou um espao. Ou seja, os caracteres 9, 10, 11, 12, 13 e 32 so brancos e as constantes correspondentes so \t , \n , \v , \f , \r e . A funo scanf trata todos os brancos como se fossem , assim como outras funes tambm o fazem. O especicador de converso %c permite que as funes scanf e printf de entrada e sada de dados, respectivamente, possam ler ou escrever um nico caracter. importante reiterar que a funo scanf no salta caracteres brancos antes de ler um caracter. Se o prximo caracter a ser lido foi digitado como um espao ou uma mudana de linha, ento a varivel correspondente conter um espao ou uma mudana de linha. Para forar a funo scanf desconsiderar espaos em branco antes da leitura de um caracter, necessrio adicionar um espao na cadeia de caracteres de formatao de leitura antes do especicador de formatao %c , como a seguir:
Um espao na cadeia de caracteres de formatao de leitura signica que haver um salto de zero ou mais caracteres brancos. O programa 18.2 mostra um exemplo de uso da funo scanf e de uma cadeia de caracteres de formatao contendo um especicador de tipo %c . Programa 18.2: Conta o nmero de vogais minsculas na frase digitada pelo usurio.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
#include <stdio.h> int main(void) { char c; int conta; printf("Digite uma frase: "); conta = 0; do { scanf("%c", &c); if (c == a || c == e || c == i || c == o || c == u) conta++; } while (c != \n); printf("A frase tem %d vogais minsculas\n", conta); return 0; }
A funo scanf tem compreenso dicultada em alguns casos, especialmente quando queremos ralizar a entrada de caracteres ou ainda quando misturamos a entrada de nmeros e caracteres em um programa. A funo scanf essencialmente uma funo de casamento
DCT UFMS
18.3 C ARACTERES
107
de cadeias de caracteres que tenta emparelharar grupos de caracteres com especicaes de converso. Essa funo tambm controlada por uma cadeia de caracteres de formatao e, quando chamada, comea o processamento da informao nessa cadeia a partir do caracter mais a esquerda. Para cada especicao de converso na cadeia de caracteres de formatao, a funo scanf tenta localizar um item do tipo apropriado na cadeia de entrada, saltando brancos se necessrio. scanf l ento esse item, parando quando encontra um caracter que que no pode pertencer quele item. Se o item foi lido corretamente, a funo scanf continua processando o restante da cadeia de caracteres de formatao. Se qualquer item no pode ser lido com sucesso, a funo scanf pra imediatamente sem olhar para o resto da cadeia de caracteres de formatao e o restante da cadeia de entrada. Considere, por exemplo, que temos duas variveis do tipo int com identicadores a e b e duas variveis do tipo float com identicadores x e y . Considere a seguinte chamada funo scanf :
Suponha que o usurio informou os valores correspondentes a essas variveis da seguinte forma:
Como, nesse caso, a leitura busca por nmeros, a funo ignora os brancos. Dessa forma, os nmeros acima podem ser colocados todos em uma linha, separados por espaos, ou em vrias linhas, como mostramos acima. A funo scanf enxerga a entrada acima como uma cadeia de caracteres como abaixo: onde representa o caracter espao e representa o caracter de mudana de linha (ou a tecla Enter ). Como a funo salta os brancos, ento os nmeros so lidos de forma correta.
-102 0.33 27.8e3
Se a cadeia de caracteres de formatao de leitura contm caracteres usuais alm dos especicadores de tipo, ento o casamento dessa cadeia de caracteres e da cadeia de caracteres de entrada se d de forma ligeiramente diferente. O processamento de um caracter usual na cadeia de caracteres de formatao de uma funo scanf depende se o caracter um branco ou no. Ou seja, um ou mais brancos consecutivos na cadeia de caracteres de formatao faz com que a funo scanf leia repetidamente brancos na entrada at que um caracter no branco seja lido; o nmero de brancos na cadeia de caracteres de formatao irrelevante;
DCT
UFMS
18.3 C ARACTERES
108
outros caracteres na cadeia de caracteres de formatao, no brancos, faz com que a funo scanf o compare com o prximo caracter da entrada; se os dois caracteres so idnticos, a funo scanf descarta o caracter de entrada e continua o processamento da cadeia de caracteres de formatao; seno, a funo scanf aborta sua execuo no processando o restante da cadeia de caracteres de formatao e os caracteres de entrada. Por exemplo, suponha que temos uma leitura como abaixo:
ento, a funo scanf salta o primeiro espao, associa %d com 2 , casa / e / , salta o espao e associa %d com 33 . Por outro lado, se a entrada
2 / 33
a funo scanf salta o espao, associa %d com 2 , tenta casar o caracter / da cadeia de da entrada e como esses caracteres no casam, a caracteres de formatao com um espao funo termina e o restante / 33 da entrada permanecer armazenado na memria at que uma prxima chamada funo scanf seja realizada. Por convenincia e simplicidade, muitas vezes preferimos usar funes mais simples de entrada e sada de caracteres. As funes getchar e putchar so funes da biblioteca padro de entrada e sada da linguagem C e so usadas exclusivamente com caracteres. A funo getchar l um caracter da entrada e devolve esse caracter. A funo putchar toma um caracter como parmetro e o exibe na sada. Dessa forma, se c uma varivel do tipo char as linhas a seguir:
c = getchar(); putchar(c);
Observe que a funo getchar no tem parmetros de entrada, mas, por ser uma funo, carrega os parnteses como suxo. Alm disso, essa funo devolve um caracter, que pode ser usado em uma expresso. Nas linhas acima, getchar usada em uma atribuio. Por outro lado, a funo putchar tem como parmetro uma expresso do tipo caracter que, aps avaliada, seu valor ser exibido na sada. A funo putchar no devolve qualquer valor.
DCT UFMS
18.4 C ONVERSO
DE TIPOS
109
18.4 C ONVERSO
DE TIPOS
110
importante alertar para um caso em que uma operao envolve um dos operandos com sinal e o outro sem sinal. Pelas regras acima, o operando com sinal convertido para um operando sem sinal. Um exemplo simples dessa regra comentado a seguir. Se um dos operandos do tipo int e contm um nmero negativo e o outro operando do tipo unsigned int e contm um nmero positivo, ento uma operao envolvendo esses dois operandos deve ser realizada cuidadosamente. Nesse caso, o valor do tipo int ser promovido para o tipo unsigned int , mas a converso ser feita usando a frmula k + 232 , onde k o valor com sinal. Esse problema uma das causas de erro mais difceis de depurar. Veja o programa 18.3. Programa 18.3: Diculdade na converso de valores em expresses.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
#include <stdio.h> int main(void) { int i; unsigned int j; i = -1; j = 1; printf("i = %+d i = %u\n", i, (unsigned int) i); printf("j = %+d j = %u\n", (int) j, j); if (i < j) printf("i < j\n"); else printf("j < i\n"); return 0; }
As converses implcitas da linguagem C so bastante convenientes, como pudemos perceber at aqui. No entanto, muitas vezes necessrio que o programador tenha maior controle sobre as converses a serem realizadas, podendo assim realizar converses explcitas. Dessa forma, a linguagem C possui operadores de converso de tipo, ou casts, que j tomamos contato especialmente na aula 14. Um operador de converso de tipo um operador unrio que tem o seguinte formato geral:
(nome-do-tipo) expresso
DCT
UFMS
18.5 T IPOS
111
onde nome-do-tipo o nome de qualquer tipo bsico de dados que ser usado para converter o valor da expresso , aps sua avaliao. Por exemplo, se f e frac so variveis do tipo float , ento o trecho de cdigo a seguir:
onde tipo-bsico o nome de um dos tipos de dados bsicos da linguagem C e tipo-novo o nome do novo tipo de dados denido pelo programador. Um exemplo de denio e uso de um tipo apresentado a seguir:
Na primeira linha, typedef uma palavra reservada da linguagem C, int o tipo bsico de dados para nmeros inteiros e Logic o nome do novo tipo de dados criado pelo programador. Na segunda linha, ocorre a declarao de duas variveis do tipo Logic : a varivel primo e a varivel crescente . O nome Logic desse novo tipo foi descrito com a primeira letra maiscula, mas isso no uma obrigatoriedade. O novo tipo Logic criado a partir de typedef faz com que o compilador o adicione a sua lista de tipos conhecidos. Isso signica que podemos, a partir de ento, declarar variveis com esse tipo novo, us-lo como operador conversor de tipo, etc. O compilador trata Logic como um sinnimo para int . Dessa forma, as variveis primo e crescente so, na verdade, variveis do tipo int . Denies de tipos podem fazer com que o cdigo seja mais compreensvel, mais fcil de modicar e mais fcil de transportar de uma arquitetura para outra.
DCT
UFMS
112
sizeof (nome-do-tipo)
determina um valor que um inteiro sem sinal representando o nmero de bytes necessrios para armazenar um valor do tipo dado por nome-do-tipo . Veja o programa 18.4, que mostra as quantidades de bytes necessrios para armazenamento de valores dos tipos bsicos da linguagem C. Programa 18.4: Exemplo do uso do operador unrio sizeof.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
#include <stdio.h> int main(void) { printf("Caracter: %d\n", sizeof(char)); printf("Inteiros:\n"); printf(" short: %d\n", sizeof(short int)); printf(" int: %d\n", sizeof(int)); printf(" long int: %d\n", sizeof(long int)); printf("Nmeros de ponto flutuante:\n"); printf(" float: %d\n", sizeof(float)); printf(" double: %d\n", sizeof(double)); printf(" long double: %d\n", sizeof(long double)); return 0; }
Caracter: 1 Inteiros: short: 2 int: 4 long int: 4 Nmeros de ponto flutuante: float: 4 double: 8 long double: 12
DCT
UFMS
18.7 E XERCCIOS
113
18.7 Exerccios
Exerccios para treinar os conceitos aprendidos nesta aula. Faa muitos testes com suas entradas e sadas. 18.1 Dadas n triplas compostas por um smbolo de operao aritmtica (+, , ou /) e dois nmeros reais, calcule o resultado ao efetuar a operao indicada para os dois nmeros. Faremos esse exerccio usando a estrutura condicional switch , que compara uma expresso com uma seqncia de valores. Essa estrutura tem o seguinte formato:
switch (expresso) { case constante: seqncia de comandos break; case constante: seqncia de comandos break; ... default: seqncia de comandos break; }
Veja o programa 18.5. Observe especialmente a leitura dos dados. 18.2 Um matemtico italiano da idade mdia conseguiu modelar o ritmo de crescimento da populao de coelhos atravs de uma seqncia de nmeros naturais que passou a ser conhecida como seqncia de Fibonacci. A seqncia de Fibonacci descrita pela seguinte frmula de recorrncia: F1 = 1 F2 = 1 Fi = Fi1 + Fi2 , para i 3. Escreva um programa que dado n 1 calcule e exiba Fn . 18.3 Os babilnios descreveram a mais de 4 mil anos um mtodo para calcular a raiz quadrada de um nmero. Esse mtodo cou posteriormente conhecido como mtodo de Newton. Dado um nmero x, o mtodo parte de um chute inicial y para o valor da raiz quadrada de x e sucessivamente encontra aproximaes desse valor calculando a mdia aritmtica de y e de x/y . O exemplo a seguir mostra o mtodo em funcionamento para o clculo da raiz quadrada de 3, com chute inicial 1: x 3 3 3 3 3
DCT
18.7 E XERCCIOS
114
#include <stdio.h> int main(void) { char operador; int n; float op1, op2, result; printf("Informe n: "); scanf("%d", &n); for ( ; n > 0; n--) { printf("Informe a expresso (ex.: 5.3 * 3.1): "); scanf("%f %c%f", &op1, &operador, &op2); switch (operador) { case +: result = op1 + op2; break; case -: result = op1 - op2; break; case *: result = op1 * op2; break; case /: result = op1 / op2; break; default: break; } printf("%f %c %f = %f\n", op1, operador, op2, result); } return 0; }
Escreva um programa que receba um nmero real positivo x e um nmero real e calcule a raiz quadrada de x usando o mtodo de Newton, at que o valor absoluto da diferena entre dois valores consecutivos de y seja menor que . Mostre tambm na sada a quantidade de passos realizados para obteno da raiz de x.
DCT
UFMS
A ULA 19
V ETORES
At o momento, estudamos os tipos de dados bsicos ou elementares da linguagem C de programao: char , int , float e double . Os tipos de dados bsicos se caracterizam pelo fato que seus valores no podem ser decompostos. Por outro lado, se os valores de um tipo de dados podem ser decompostos ou subdivididos em valores mais simples, ento o tipo de dados chamado de complexo, composto ou estruturado. A organizao desses valores e as relaes estabelecidas entre eles determinam o que conhecemos como estrutura de dados. Estudaremos algumas dessas estruturas de dados em nossas aulas futuras, como por exemplo as listas lineares, pilhas e las. Nesta aula iniciaremos o estudo sobre variveis compostas, partindo de uma varivel conhecida como varivel composta homognea unidimensional ou simplesmente vetor. Caractersticas especcas da linguagem C no tratamento de vetores tambm sero abordadas.
19.1 Motivao
Como um exemplo da necessidade do uso da estrutura de dados conhecida como vetor, considere o seguinte problema: Dadas cinco notas de uma prova dos(as) estudantes de uma disciplina, calcular a mdia das notas da prova e a quantidade de estudantes que obtiveram nota maior que a mdia e a quantidade de estudantes que obtiveram nota menor que a mdia. Uma tentativa natural e uma idia inicial para solucionar esse problema consiste no uso de uma estrutura de repetio para acumular o valor das cinco notas informadas e o clculo posterior da mdia destas cinco notas. Esses passos resolvem o problema inicial do clculo da mdia das provas dos estudantes. Mas e a computao das quantidades de alunos que obtiveram nota maior e menor que a mdia j computada? Observe que aps lidas as cinco notas e processadas para o clculo da mdia em uma estrutura de repetio, a tarefa de encontrar as quantidades de estudantes que obtiveram nota superior e inferior mdia impossvel, j que as notas dos estudantes no esto mais disponveis na memria. Isto , a menos que o(a) programador(a) pea ao usurio para informar as notas dos(as) estudantes novamente, no h como computar essas quantidades. Apesar disso, ainda podemos resolver o problema usando uma estrutura seqencial em que todas as cinco notas informadas pelo usurio cam armazenadas na memria e assim podemos posteriormente computar as quantidades solicitadas consultando estes valores. Veja o programa 19.1. 115
19.1 M OTIVAO
116
#include <stdio.h> int main(void) { int menor, maior; float nota1, nota2, nota3, nota4, nota5, media; printf("Informe as notas dos alunos: "); scanf("%f%f%f%f%f%f", ¬a1, ¬a2, ¬a3, ¬a4, ¬a5); media = (nota1 + nota2 + nota3 + nota4 + nota5) / 5; printf("\nMedia das provas: %2.2f\n", media); menor = 0; if (nota1 < media) menor++; if (nota2 < media) menor++; if (nota3 < media) menor++; if (nota4 < media) menor++; if (nota5 < media) menor++; maior = 0; if (nota1 > media) maior++; if (nota2 > media) maior++; if (nota3 > media) maior++; if (nota4 > media) maior++; if (nota5 > media) maior++; printf("\nQuantidade de estudantes com nota inferior mdia: %d\n", menor); printf("\nQuantidade de estudantes com nota superior mdia: %d\n", maior); return 0; }
A soluo que apresentamos no programa 19.1 de fato uma soluo para o problema. Isto , dadas como entrada as cinco notas dos(as) estudantes em uma prova, o programa computa de forma correta as sadas que esperamos, ou seja, as quantidades de estudantes com nota inferior e superior mdia da prova. No entanto, o que aconteceria se a sala de aula tivesse mais estudantes, como por exemplo 100? Ou 1.000 estudantes? Certamente, a estrutura seqencial no seria apropriada para resolver esse problema, j que o programador teria de digitar centenas ou milhares de linhas repetitivas, incorrendo inclusive na possibilidade de propagao de erros e na diculdade de encontr-los. E assim como esse, outros problemas no podem ser resolvidos sem uma extenso na maneira de armazenar e manipular as entradas de dados.
DCT UFMS
19.2 D EFINIO
117
19.2 Denio
Uma varivel composta homognea unidimensional, ou simplesmente um vetor, uma estrutura de armazenamento de dados que se dispe de forma linear na memria e usada para armazenar valores de um mesmo tipo. Um vetor ento uma lista de clulas na memria de tamanho xo cujos contedos so do mesmo tipo bsico. Cada uma dessas clulas armazena um, e apenas um, valor. Cada clula do vetor tem um endereo ou ndice atravs do qual podemos referenci-la. O termo varivel composta homognea unidimensional bem explcito e signica que temos uma: (i) varivel, cujos valores podem ser modicados durante a execuo de um programa; (ii) composta, j que h um conjunto de valores armazenado na varivel; (iii) homognea, pois os valores armazenados na varivel composta so todos de um mesmo tipo bsico; e (iv) unidimensional, porque a estrutura de armazenamento na varivel composta homognea linear. No entanto, pela facilidade, usamos o termo vetor com o mesmo signicado. A forma geral de declarao de um vetor dada a seguir:
tipo identificador[tamanho];
onde: tipo um tipo de dados conhecido ou denido pelo(a) programador(a); identificador o nome do vetor, fornecido pelo(a) programador(a); e tamanho a quantidade de clulas a serem disponibilizadas para uso no vetor. Por exemplo, a declarao a seguir
float nota[100];
faz com que 100 clulas contguas de memria sejam reservadas, cada uma delas podendo armazenar nmeros de ponto utuante do tipo float . A referncia a cada uma dessas clulas realizada pelo identicador do vetor nota e por um ndice. Na gura 19.1 mostramos o efeito da declarao do vetor nota na memria de um computador.
0 1 2 98 99
memria
nota
DCT
UFMS
19.3 I NICIALIZAO
118
importante observar que, na linguagem C, a primeira clula de um vetor tem ndice 0, a segunda clula tem ndice 1, a terceira tem ndice 2 e assim por diante. Para referenciar o contedo da clula 0 do vetor nota , devemos usar o identicador do vetor e o ndice 0, envolvido por colchetes, isto , nota[0] . Assim, o uso de nota[35] em uma expresso qualquer referencia o trigsimo sexto elemento do vetor nota . Podemos, tambm, usar uma varivel do tipo inteiro como ndice de um vetor ou ainda uma expresso aritmtica do tipo inteiro. Por exemplo, nota[i] acessa a (i + 1)-sima clula do vetor nota , supondo que a varivel i do tipo inteiro. Ou ainda, podemos fazer nota[2*i+j] para acessar a posio de ndice 2i + j do vetor nota , supondo que i e j so variveis do tipo inteiro contm valores compatveis de tal forma que o resultado da expresso aritmtica 2i + j seja um valor no intervalo de ndices vlido para o vetor nota . O compilador da linguagem C no verica de antemo se os limites dos ndices de um vetor esto corretos, trabalho que deve ser realizado pelo(a) programador(a). Um erro comum, aparentemente inocente, mas que pode ter causas desastrosas, mostrado no trecho de cdigo abaixo:
Alguns compiladores podem fazer com que a estrutura de repetio for acima seja executada innitamente. Isso porque quando a varivel i atinge o valor 10 , o programa armazena o valor 0 em A[10] . Observe, no entanto, que A[10] no existe e assim 0 armazenado no compartimento de memria que sucede A[9] . Se a varivel i ocorre na memria logo aps A[9] , como bem provvel, ento i receber o valor 0 fazendo com que o lao inicie novamente.
19.3 Inicializao
Podemos atribuir valores iniciais a quaisquer variveis de qualquer tipo bsico no momento de suas respectivas declaraes. At o momento, no havamos usado declaraes e inicializaes em conjunto. O trecho de cdigo abaixo mostra declaraes e inicializaes simultneas de variveis de tipos bsicos:
No exemplo acima, as variveis c , soma e produto so inicializadas no momento da declarao, enquanto que num e resultado so apenas declaradas.
DCT UFMS
19.3 I NICIALIZAO
119
Apesar de, por alguns motivos, no termos usado declaraes e inicializaes simultneas com variveis de tipos bsicos, essa caracterstica da linguagem C muito favorvel quando tratamos de variveis compostas. Do mesmo modo, podemos atribuir um valor inicial a um vetor no momento de sua declarao. As regras para declarao e atribuio simultneas para vetores so um pouco mais complicadas, mas veremos as mais simples no momento. A forma mais comum de se fazer a inicializao de um vetor atravs de uma lista de expresses constantes envolvidas por chaves e separadas por vrgulas, como no exemplo abaixo:
Se o inicializador tem menos elementos que a capacidade do vetor, os elementos restantes so inicializados com o valor 0 (zero), como abaixo:
No entanto, no permitido que o inicializador tenha mais elementos que a quantidade de compartimentos do vetor. Podemos ento facilmente inicializar um vetor todo com zeros da seguinte maneira:
Se um inicializador est presente em conjunto com a declarao de um vetor, ento o seu tamanho pode ser omitido, como mostrado a seguir:
O compilador ento interpreta que o tamanho do vetor A determinado pela quantidade de elementos do seu inicializador. Isso signica que, aps a execuo da linha do exemplo de cdigo acima, o vetor A tem 10 compartimentos de memria, do mesmo modo como se tivssemos especicado isso explicitamente, como no primeiro exemplo.
DCT UFMS
19.4 E XEMPLO
COM VETORES
120
#include <stdio.h> int main(void) { int i, menor, maior; float nota[5], soma, media; for (i = 0; i < 5; i++) { printf("Informe a nota do(a) estudante %d: ", i+1); scanf("%f", ¬a[i]); } soma = 0.0; for (i = 0; i < 5; i++) soma = soma + nota[i]; media = soma / 5; menor = 0; maior = 0; for (i = 0; i < 5; i++) { if (nota[i] < media) menor++; if (nota[i] > media) maior++; } printf("\nMedia das provas: %2.2f\n", media); printf("Quantidade de alunos com nota inferior mdia: %d\n", menor); printf("Quantidade de alunos com nota superior mdia: %d\n", maior); return 0; }
Observe que o programa 19.2 mais conciso e de mais fcil compreenso que o programa 19.1. O programa tambm mais facilmente extensvel, no sentido que muito poucas modicaes so necessrias para que esse programa possa solucionar problemas semelhantes com outras quantidades de estudantes. Isto , se a turma tem 5, 10 ou 100 estudantes, ou ainda um nmero desconhecido n que ser informado pelo usurio durante a execuo do programa, uma pequena quantidade de esforo ser necessria para alterao de poucas linhas do programa.
DCT
UFMS
19.5 M ACROS
PARA CONSTANTES
121
onde #define uma diretiva do pr-processador da linguagem C e identificador um nome associado constante que vem logo a seguir. Observe que, por ser uma diretiva do pr-processador, a linha contendo uma denio de uma macro no nalizada por ; . Em geral, a denio de uma macro ocorre logo no incio do programa, aps as diretivas #include para incluso de cabealhos de bibliotecas de funes. Alm disso, o identicador de uma macro , preferencial mas no obrigatoriamente, descrito em letras maisculas. Exemplos de macros so apresentados a seguir:
Quando um programa compilado, o pr-processador troca cada macro denida no cdigo pelo valor que ela representa. Depois disso, um segundo passo de compilao executado. O programa 14.5 pode ser reescrito com o uso de uma macro, como podemos ver no programa 19.3, onde uma macro com identicador PI denida e usada no cdigo. Programa 19.3: Clculo da rea do crculo.
1 2 3 4 5 6 7 8 9 10 11
#include <stdio.h> #define PI 3.141592f int main(void) { double raio, area; printf("Digite o valor do raio: "); scanf("%lf", &raio); area = PI * raio * raio; printf("A rea do crculo de raio %f %f\n", raio, area); return 0; }
DCT
UFMS
19.5 M ACROS
PARA CONSTANTES
122
O uso de macros com vetores bastante til porque, como j vimos, um vetor faz alocao esttica da memria, o que signica que em sua declarao ocorre uma reserva prvia de um nmero xo de compartimentos de memria. Por ser uma alocao esttica, no h possibilidade de aumento ou diminuio dessa quantidade aps a execuo da linha de cdigo contendo a declarao do vetor. O programa 19.2 pode ser ainda modicado como no programa 19.4 com a incluso de uma macro que indica a quantidade de notas a serem processadas. Programa 19.4: Soluo do problema proposto na seo 19.1 usando uma macro e um vetor.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
#include <stdio.h> #define MAX 5 int main(void) { int i, menor, maior; float nota[MAX], soma, media; for (i = 0; i < MAX; i++) { printf("Informe a nota do(a) estudante %d: ", i+1); scanf("%f", ¬a[i]); } soma = 0.0; for (i = 0; i < MAX; i++) soma = soma + nota[i]; media = soma / MAX; menor = 0; maior = 0; for (i = 0; i < MAX; i++) { if (nota[i] < media) menor++; if (nota[i] > media) maior++; } printf("\nMedia das provas: %2.2f\n", media); printf("Quantidade de estudantes com nota inferior mdia: %d\n", menor); printf("Quantidade de estudantes com nota superior mdia: %d\n", maior); return 0; }
A macro MAX usada quatro vezes no programa 19.4: na declarao do vetor nota , nas expresses relacionais das duas estruturas de repetio for e no clculo da mdia. A vantagem de se usar uma macro que, caso seja necessrio modicar a quantidade de notas do programa por novas exigncias do usurio, isso pode ser feito rpida e facilmente em uma nica linha do cdigo, onde ocorre a denio da macro. Voltaremos a discutir mais sobre macros, sobre a diretiva #define e tambm sobre outras diretivas do pr-processador na aula 46.
DCT
UFMS
19.5 M ACROS
PARA CONSTANTES
123
Exerccios
19.1 Dada uma seqncia de n nmeros inteiros, com 1 inversa de leitura. n 100, imprimi-la em ordem
#include <stdio.h> #define MAX 100 int main(void) { int i, n, A[MAX]; printf("Informe n: "); scanf("%d", &n); for (i = 0; i < n; i++) { printf("Informe o nmero %d: ", i+1); scanf("%d", &A[i]); } printf("Nmeros na ordem inversa da leitura:\n"); for (i = n-1; i >= 0; i--) printf("%d ", A[i]); printf("\n"); return 0; }
19.2 Uma prova consta de 30 questes, cada uma com cinco alternativas identicadas pelas letras A, B, C, D e E. Dado o carto gabarito da prova e o carto de respostas de n estudantes, com 1 n 100, computar o nmero de acertos de cada um dos estudantes. 19.3 Tentando descobrir se um dado era viciado, um dono de cassino o lanou n vezes. Dados os n resultados dos lanamentos, determinar o nmero de ocorrncias de cada face. 19.4 Um jogador viciado de cassino deseja fazer um levantamento estatstico simples sobre uma roleta. Para isso, ele fez n lanamentos nesta roleta. Sabendo que uma roleta contm 37 nmeros (de 0 a 36), calcular a freqncia de cada nmero desta roleta nos n lanamentos realizados.
DCT
UFMS
A ULA 20
I NVARIANTES
Algoritmos e programas podem conter um ou mais processos iterativos, que so controlados por estruturas de repetio. Na linguagem C, como sabemos, as estruturas de repetio so while , for e do-while . Processos iterativos podem ser documentados com invariantes, que nos ajudam a entender os motivos que o algoritmo e/ou programa est correto. Nesta aula deniremos invariantes, mostraremos exemplos de invariantes e, atravs deles, provaremos que os programas apresentados esto corretos.
20.1 Denio
Um invariante de um processo iterativo uma relao entre os valores das variveis que vale no incio de cada iterao desse processo. Os invariantes explicam o funcionamento do processo iterativo e permitem provar por induo que ele tem o efeito desejado. Devemos provar trs elementos sobre um invariante de um processo iterativo: Inicializao: verdadeiro antes da primeira iterao da estrutura de repetio; Manuteno: se verdadeiro antes do incio de uma iterao da estrutura de repetio, ento permanece verdadeiro antes da prxima iterao; Trmino: quando a estrutura de repetio termina, o invariante nos d uma propriedade til que nos ajuda a mostrar que o algoritmo ou programa est correto. Quando as duas primeiras propriedades so satisfeitas, o invariante verdadeiro antes de toda iterao da estrutura de repetio. Como usamos invariantes para mostrar a corretude de um algoritmo e/ou programa, a terceira propriedade a mais importante, aquela que permite mostrar de fato a sua corretude. Dessa forma, os invariantes explicam o funcionamento dos processos iterativos e permitem provar, por induo, que esses processos tm o efeito desejado.
20.2 Exemplos
Nesta seo veremos exemplos do uso dos invariantes para mostrar a corretude de programas. O primeiro exemplo bem simples e o programa contm apenas variveis do tipo inteiro e, obviamente, uma estrutura de repetio. O segundo exemplo um programa que usa um vetor para soluo do problema. 124
20.2 E XEMPLOS
125
Considere ento o programa 20.1, que realiza a soma de n nmeros inteiros fornecidos pelo usurio. O programa 20.1 simples e no usa um vetor para solucionar esse problema. Programa 20.1: Soma n inteiros fornecidos pelo usurio.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
#include <stdio.h> int main(void) { int n, i, num, soma; printf("Informe n: "); scanf("%d", &n); soma = 0; for (i = 1; i <= n; i++) { /* a varivel soma contm a soma dos primeiros (i - 1) nmeros fornecidos pelo usurio */ printf("Informe um nmero: "); scanf("%d", &num); soma = soma + num; } printf("Soma dos %d nmeros %d\n", n, soma); return 0; }
importante destacar o comentrio nas linhas 9 e 10, as primeiras do bloco de execuo da estrutura de repetio for do programa 20.1: este o invariante desse processo iterativo. E como Feoloff destaca em [4], o enunciado de um invariante , provavelmente, o nico tipo de comentrio que vale a pena inserir no corpo de um algoritmo, programa ou funo. Ento, podemos provar a seguinte proposio. Proposio 20.1. O programa 20.1 realiza a soma de n Demonstrao. Por convenincia na demonstrao usaremos o modo matemtico para expressar as variveis do programa: n ser denotada por n, i por i, soma por soma e num por num. Quando nos referirmos ao i-simo nmero inteiro fornecido pelo usurio e armazenado na varivel num , usaremos a notao numi . Provar que o programa 20.1 est correto signica mostrar que para qualquer valor de n e qualquer seqncia de n nmeros, a varivel soma conter, ao nal do processo iterativo, o valor
n
soma =
i=1
numi .
Vamos mostrar que o invariante vale no incio da primeira iterao do processo iterativo. Como soma contm o valor 0 (zero) e i contm 1, verdade que a varivel soma contm a soma dos i 1 primeiros nmeros fornecidos pelo usurio.
DCT UFMS
20.2 E XEMPLOS Suponha agora que o invariante valha no incio da i-sima iterao, com 1 < i < n. Vamos mostrar que o invariante vale no incio da ltima iterao, quando i contm o valor n. Por hiptese de induo, a varivel soma contm o valor
n1
126
=
i=1
numi .
Ento, no decorrer dessa n-sima iterao, o usurio deve informar um nmero que ser armazenado na varivel num e, ento, a varivel soma conter o valor soma = + numn
n1
=
i=1 n
numi numi .
i=1
+ numn
Portanto, isso mostra que o programa 20.1 de fato realiza a soma dos n nmeros inteiros fornecidos pelo usurio. O prximo exemplo dado da seguinte forma: dado um vetor com n nmeros inteiros fornecidos pelo usurio, encontrar o valor mximo armazenado nesse vetor. O programa 20.2 bem simples e se prope a solucionar esse problema. Programa 20.2: Encontra o maior valor em um vetor com n nmeros inteiros fornecidos pelo usurio.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
#include <stdio.h> int main(void) { int n, vet[100], i, max; printf("Informe n: "); scanf("%d", &n); for (i = 0; i < n; i++) { printf("vet[%d]=", i); scanf("%d", &vet[i]); } max = vet[0]; for (i = 1; i < n; i++) { /* max um elemento mximo em vet[0..i-1] */ if (vet[i] > max) max = vet[i]; } printf("O elemento de mximo valor no vetor %d\n", max); return 0; }
DCT
UFMS
20.2 E XEMPLOS
127
Proposio 20.2. O programa 20.2 encontra o elemento mximo de um conjunto de n nmeros fornecidos pelo usurio. Demonstrao. Novamente, por convenincia na demonstrao usaremos o modo matemtico para expressar as variveis do programa: n ser denotada por n, i por i, vet por vet e max por max. Provar que o programa 20.2 est correto signica mostrar que para qualquer valor de n e qualquer seqncia de n nmeros fornecidos pelo usurio e armazenados em um vetor vet, a varivel max conter, ao nal do processo iterativo, o valor do elemento mximo em vet[0..n 1].
Vamos mostrar que o invariante vale no incio da primeira iterao do processo iterativo. Como max contm o valor armazenado em vet[0] e, em seguida, a varivel i inicializada com o valor 1, ento verdade que a varivel max contm o elemento mximo em vet[0..i 1]. Suponha agora que o invariante valha no incio da i-sima iterao, com 1 < i < n. Vamos mostrar que o invariante vale no incio da ltima iterao, quando i contm o valor n 1. Por hiptese de induo, no incio desta iterao a varivel max contm o valor o elemento mximo de vet[0..n 2]. Ento, no decorrer dessa iterao, o valor vet[n 1] comparado com max e dois casos devem ser avaliados: (i) vet[n 1] > max Isso signica que o valor vet[n 1] maior que qualquer valor armazenado em vet[0..n 2]. Assim, na linha 15 a varivel max atualizada com vet[n 1] e portanto a varivel max conter, ao nal desta ltima iterao, o elemento mximo da seqncia em vet[0..n 1].
(ii) vet[n 1] max Isso signica que existe pelo menos um valor em vet[0..n 2] que maior ou igual a vet[n 1]. Por hiptese de induo, esse valor est armazenado em max. Assim, ao nal desta ltima iterao, a varivel max conter o elemento mximo da seqncia em vet[0..n 1]. Portanto, isso mostra que o programa 20.2 de fato encontra o elemento mximo em uma seqncia de n nmeros inteiros armazenados em um vetor.
Exerccios
20.1 O programa 20.3 recebe um nmero inteiro n > 0, uma seqncia de n nmeros inteiros, um nmero inteiro x e verica se x pertence seqncia de nmeros. Mostre que o programa 20.3 est correto. 20.2 Dado n > 0 e uma seqncia de n nmeros inteiros em um vetor, escreva um programa que inverta os elementos dessa seqncia armazenando-os no mesmo vetor.
DCT
UFMS
20.2 E XEMPLOS
128
#include <stdio.h> int main(void) { int n, A[100], i, x; printf("Informe um valor para n: "); scanf("%d", &n); for (i = 0; i < n; i++) { printf("A[%d]=", i); scanf("%d", &A[i]); } printf("Informe x: "); scanf("%d", &x); i = 0; while (i < n && A[i] != x) { /* x no pertence A[0..i] */ i++; } if (i < n) printf("%d o %d-simo elemento do vetor\n", x, i); else printf("%d no se encontra no vetor\n", x); return 0; }
20.3 O piso de um nmero x o nico inteiro i tal que i x < i +1. O piso de x denotado por x. Escreva um programa que receba um nmero inteiro positivo n e compute log2 n. Segue uma amostra de valores: n log2 n 15 3 16 4 31 4
32 5
63 5
64 6
127 6
128 7
255 7
256 8
511 8
512 9
Documente seu programa enunciando o invariante do processo iterativo. Prove que seu programa est correto.
DCT
UFMS
A ULA 21
E XERCCIOS
Enunciados
21.1 Dados dois vetores x e y , ambos com n elementos, 1 escalar desses vetores. n 100, determinar o produto
#include <stdio.h> int main(void) { int i, n; float prod, x[100], y[100]; printf("Informe n: "); scanf("%d", &n); for (i = 0; i < n; i++) { printf("Informe x[%d]: ", i+1); scanf("%f", &x[i]); } for (i = 0; i < n; i++) { printf("Informe y[%d]: ", i+1); scanf("%f", &y[i]); } prod = 0.0; for (i = 0; i < n; i++) prod = prod + x[i] * y[i]; printf("Produto escalar dos vetores x e y %f\n", prod); return 0; }
21.2 So dadas as coordenadas reais x e y de um ponto, um nmero natural n e as coordenadas reais de n pontos, com 1 n 100. Deseja-se calcular e imprimir sem repetio os raios das circunferncias centradas no ponto (x, y ) que passem por pelo menos um dos n pontos dados. 129
130 Exemplo: (x, y ) = (1.0, 1.0) n=5 Pontos: (1.0, 1.2), (1.5, 2.0), (0.0, 2.0), (0.0, 0.5), (4.0, 2.0) Nesse caso h trs circunferncias de raios 1.12, 2.01 e 3.162. (a) A distncia entre os pontos (a, b) e (c, d) (a c)2 + (b d)2 .
Observaes:
(b) Dois pontos esto na mesma circunferncia se esto mesma distncia do centro. 21.3 Dadas duas seqncias de caracteres (uma contendo uma frase e outra contendo uma palavra), determine o nmero de vezes que a palavra ocorre na frase. Considere que essas seqncias tm no mximo 100 caracteres cada uma. Exemplo: Para a palavra ANA e a frase: ANA E MARIANA GOSTAM DE BANANA. Temos que a palavra ocorre 4 vezes na frase. 21.4 Calcule o valor do polinmio p(x) = a0 + a1 x + . . . + an xn em k pontos distintos. So dados os valores de n (grau do polinmio), com 1 n 100, de a0 , a1 , . . . an (coecientes reais do polinmio), de k e dos pontos x1 , x2 , . . . , xk . 21.5 Dado o polinmio p(x) = a0 + a1 x + . . . an xn , isto , os valores de n e de a0 , a1 , . . . , an , com 1 n 100 determine os coecientes reais da primeira derivada de p(x). 21.6 Dados dois polinmios reais p(x) = a0 + a1 x + . . . + an xn e q (x) = b0 + b1 x + . . . + bm xm m, n 100.
21.7 Dadas duas seqncias com n nmeros inteiros entre 0 e 9, interpretadas como dois nmeros inteiros de n algarismos, 1 n 100, calcular a seqncia de nmeros que representa a soma dos dois inteiros. Exemplo: n = 8, 1a seqncia 2a seqncia 8 3 1 2 3 6 4 7 1 3 5 8 4 2 6 2 3 5 5 3 8 1 7 8
+ 1
DCT
UFMS
A ULA 22
E XERCCIOS
Enunciados
22.1 Dada uma seqncia de n nmeros inteiros, com 1 n 100, imprimi-la em ordem no decrescente de seus valores. Escreva trs solues usando o mtodo da bolha, o mtodo da insero e o mtodo da seleo em cada uma delas. Programa 22.1: Programa para solucionar o exerccio 22.1 usando o mtodo da bolha.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
#include <stdio.h> int main(void) { int i, j, n, aux, A[100]; printf("Informe n: "); scanf("%d", &n); for (i = 0; i < n; i++) { printf("Informe A[%d]: ", i+1); scanf("%d", &A[i]); } for (i = 0; i < n - 1; i++) for (j = 0; j < n - 1 - i; j++) if (A[j] > A[j + 1]) { aux = A[j]; A[j] = A[j + 1]; A[j + 1] = aux; } for (i = 0; i < n; i++) printf("%d ", A[i]); printf("\n"); return 0; }
Escreva os programas para soluo deste exerccio usando o mtodo da insero e o mtodo da seleo. 131
132 22.2 Dizemos que uma seqncia de n elementos, com n par, balanceada se as seguintes somas so todas iguais: a soma do maior elemento com o menor elemento; a soma do segundo maior elemento com o segundo menor elemento; a soma do terceiro maior elemento com o terceiro menor elemento; e assim por diante . . . Exemplo: 2 12 3 6 16 15 uma seqncia balanceada, pois 16 + 2 = 15 + 3 = 12 + 6. Dados n (n par e 0 n seqncia balanceada. 100) e uma seqncia de n nmeros inteiros, vericar se essa
22.3 Dados dois nmeros naturais m e n, com 1 m, n 100, e duas seqncias ordenadas com m e n nmeros inteiros, obter uma nica seqncia ordenada contendo todos os elementos das seqncias originais sem repetio. 22.4 Dada uma seqncia x1 , x2 , . . . , xk de nmeros inteiros, com 1 k 100, verique se existem dois segmentos consecutivos iguais nesta seqncia, isto , se existem i e m tais que xi , xi+1 , . . . , xi+m1 = xi+m , xi+m+1 , . . . , xi+2m1 . Imprima, caso existam, os valores de i e m. Exemplo: Na seqncia 7, 9, 5, 4, 5, 4, 8, 6 existem i = 3 e m = 2.
DCT
UFMS
A ULA 23
C ADEIAS
DE CARACTERES
Veremos nesta aula uma estrutura que possui tratamento especial na linguagem C: a cadeia de caracteres. Esta estrutura similar a um vetor de caracteres, diferenciando-se apenas por conter um caracter especial no nal, aps o ltimo caracter vlido. Essa caracterstica evita, em muitos casos, que tenhamos de manter uma varivel que contenha o comprimento do vetor para saber o nmero de caracteres contidos nele. Outras caractersticas e diferenas importantes sero vistas a seguir. Na aula 45 veremos funes da biblioteca string.h . Essa biblioteca da linguagem C contm diversas funes teis para manipulao de cadeias de caracteres.
23.1 Literais
Vimos tomando contato com cadeias de caracteres desde quando escrevemos nosso primeiro programa na linguagem C. Por exemplo, na sentena abaixo:
printf("Programar bacana!\n");
o nico argumento passado para a funo printf a cadeia de caracteres (de formatao) "Programar bacana!\n" . As aspas duplas so usadas para delimitar uma constante do tipo cadeia de caracteres, que pode conter qualquer combinao de letras, nmeros ou caracteres especiais que no sejam as aspas duplas. Mesmo assim, possvel inserir as aspas duplas no interior de uma constante cadeia de caracteres, inserindo a seqncia \" nessa cadeia. Na linguagem C, uma constante do tipo cadeia de caracter chamada de literal. Quando estudamos o tipo de dados char , aprendemos que uma varivel deste tipo pode conter apenas um nico caracter. Para atribuir um caracter a uma varivel, o caracter deve ser envolvido por aspas simples. Dessa forma, o trecho de cdigo a seguir:
133
23.2 V ETORES
DE CARACTERES
134
tem o efeito de atribuir o caractere cuja constante + para a varivel sinal . Alm disso, aprendemos que existe uma distino entre as aspas simples e as aspas duplas, sendo que no primeiro caso elas servem para denir constantes do tipo caracter e no segundo para denir constantes do tipo cadeia de caracteres. Assim, o seguinte trecho de cdigo:
no est correto, j que que a varivel sinal foi declarada do tipo char , podendo conter um nico caracter. Lembre-se que na linguagem C as aspas simples e as aspas duplas so usadas para denir dois tipos de constantes diferentes. Usamos literais especialmente quando chamamos as funes printf e scanf em um programa, ou seja, quando descrevemos cadeias de caracteres de formatao. Essencialmente, a linguagem C trata as literais como cadeias de caracteres. Quando o compilador da linguagem C encontra uma literal com n caracteres em um programa, ele reserva n + 1 compartimentos de memria para armazenar a cadeia de caracteres correspondente. Essa rea na memria conter os caracteres da cadeia mais um caractere extra, o caractere nulo, que registra o nal da cadeia. O caractere nulo um byte cujos bits so todos 0 (zeros) e representado pela seqncia \0 . importante destacar a diferena entre o caractere nulo e o caractere zero: o primeiro um caractere no-imprimvel, tem valor decimal 0 e constante \0 ; o segundo um caractere imprimvel, tem valor 48, smbolo grco 0 e constante 0 . A literal "abc" armazenada como um vetor de quatro caracteres na memria, como mostra a gura 23.1.
a b c \0
Figura 23.1: Armazenamento de uma literal na memria. Por outro lado, uma literal tambm pode ser vazia. A literal "" uma literal vazia, representada na memria como na gura 23.2.
\0
23.3 C ADEIAS
DE CARACTERES
135
Para ler, por exemplo, o contedo da varivel palavra , produzimos o seguinte trecho de programa:
Para imprimir o contedo dessa mesma varivel palavra , devemos escrever um trecho de programa da seguinte forma:
Note que sempre necessitamos de uma varivel adicional para controlar o comprimento de um vetor de caracteres quando da sua leitura. A varivel m faz esse papel no primeiro trecho de programa acima. Alm disso, tanto para leitura como para escrita de um vetor de caracteres, precisamos de uma estrutura de repetio para processar os caracteres um a um. Com as cadeias de caracteres, evitamos esta sobrecarga de trabalho para o programador, como veremos daqui por diante.
DCT
UFMS
23.3 C ADEIAS
DE CARACTERES
136
Suponha que necessitamos de uma varivel capaz de armazenar uma cadeia de caracteres de at 50 caracteres. Como a cadeia de caracteres necessitar de um caractere nulo no nal, ento a cadeia de caracteres tm de ser declarada com 51 compartimentos para armazenar valores do tipo char , como mostrado a seguir:
Na declarao de uma varivel que pode armazenar uma cadeia de caracteres, sempre devemos reservar um compartimento a mais para que o caractere nulo possa ser armazenado. Como diversas funes da linguagem C supem que as cadeias de caracteres so terminadas com o caractere nulo, se isso no ocorre em algum caso, o comportamento do programa passa a ser imprevisvel. A declarao de um vetor de caracteres com dimenso MAX+1 no quer dizer que ele sempre conter uma cadeia de caracteres com MAX caracteres. O comprimento de uma cadeia de caracteres depende da posio do caractere nulo na cadeia, no do comprimento do vetor onde a cadeia est armazenada. Como em qualquer vetor, uma cadeia de caracteres tambm pode ser declarada e inicializada simultaneamente. Por exemplo,
faz com que o compilador insira seqencialmente os caracteres da cadeia de caracteres "Campo Grande" no vetor cidade e ento adicione o caractere nulo ao nal da cadeia. Apesar de "Campo Grande" parecer uma literal, na realidade, a linguagem C a enxerga como uma abreviao para um inicializador de um vetor, que poderia ter sido escrito equivalentemente como abaixo:
Se um inicializador tem menor comprimento que o comprimento do vetor, o compilador preencher os caracteres restantes do vetor com o caractere nulo. Se, por outro lado, o inicializador tem maior comprimento que a capacidade de armazenamento do vetor associado, os caracteres iniciais do inicializador sero armazenados no vetor, sem que o ltimo deles seja o caractere nulo, impedindo assim que essa varivel seja usada como uma legtima cadeia de caracteres. Vejamos agora o programa 23.1 que determina o comprimento de uma varivel que uma cadeia de caracteres. Qual ser a sada deste programa?
DCT
UFMS
23.3 C ADEIAS
DE CARACTERES
137
#include <stdio.h> int main(void) { char palavra[10] = "Ola!"; int n; n = 0; while(palavra[n] != \0) n++; printf("O comprimento da palavra %d\n", n); return 0; }
H uma forma de ler e escrever cadeias de caracteres na linguagem C que facilita o trabalho de um(a) programador(a), evitando que sempre lance mo de uma estrutura de repetio para realizar uma dessas duas tarefas. O especicador de converso %s no interior de uma cadeia de caracteres de formatao de entrada pode ser usado para mostrar um vetor de caracteres que terminado por um caracter nulo, isto , uma cadeia de caracteres. Assim se palavra um vetor de caracteres terminado com o caractere nulo, a chamada da funo abaixo:
printf("%s\n", palavra);
pode ser usada para mostrar o contedo completo da cadeia de caracteres palavra na sada padro. Quando a funo printf encontra o especicador de converso %s , supe que o argumento correspondente uma cadeia de caracteres, isto , um vetor de caracteres terminado por um caractere nulo. Podemos tambm usar a mesma cadeia de caracteres de formatao "%s" para leitura de uma cadeia de caracteres. A funo scanf pode ser usada com o especicador de converso %s para ler uma cadeia de caracteres at que a leitura de um branco seja realizada. Assim, a chamada da funo scanf abaixo:
scanf("%s", palavra);
tem o efeito de ler uma cadeia de caracteres digitada pelo usurio e de armazen-la no vetor de caracteres palavra . muito importante ressaltar que, ao contrrio das chamadas anteriores da funo scanf , no caso de leitura de cadeias de caracteres, o smbolo & no adicionado como prexo do identicador da varivel. Veremos o porqu disto a partir da aula 49, quando aprenderemos apontadores.
DCT UFMS
23.3 C ADEIAS
DE CARACTERES
138
Se na execuo da funo scanf anterior um(a) usurio(a) digita os caracteres abcdefg , a cadeia de caracteres "abcdefg" armazenada no vetor palavra . Se, diferentemente, um(a) usurio(a) digita os caracteres Campo Grande , ento apenas a cadeia de caracteres "Campo" armazenada no vetor palavra , devido ao branco ( ). Os caracteres restantes da cadeia digitada caro disponveis no buffer de entrada at que uma prxima chamada funo scanf seja realizada. Para evitar os brancos na leitura de uma cadeia de caracteres, usamos o especicador de converso %[...] , que tambm usado na leitura de cadeias de caracteres, delimitando, dentro dos colchetes, quais so os caracteres permitidos em uma leitura. Qualquer outro caractere diferente dos especicados dentro dos colchetes nalizam a leitura. Alm disso, podemos inverter essas permisses, indicando o caractere ^ como o primeiro caractere dentro dos colchetes. Por exemplo,
scanf("%[^\n]", palavra);
realiza a leitura de uma cadeia de caracteres, armazenando seu contedo no vetor de caracteres palavra . O caracter que naliza a leitura o \n . Qualquer outro caracter ser lido e armazenado no vetor palavra . muito importante destacar que a funo scanf termina automaticamente uma cadeia de caracteres que lida com o especicador de converso "%s" ou "%[...]" com um caracter nulo, fazendo assim que o vetor de caracteres se torne de fato uma cadeia de caracteres aps sua leitura. Veja um exemplo simples do uso dos conceitos de entrada e sada de cadeias de caracteres no programa 23.2. Programa 23.2: Entrada e sada de uma cadeia de caracteres.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
#include <stdio.h> #define MAX 20 int main(void) { char palavra[MAX]; int n; printf("Informe uma palavra (com at %d caracteres): ", MAX); scanf("%s", palavra); n = 0; while (palavra[n] != \0) n++; printf("A palavra [%s] tem %d caracteres\n", palavra, n); return 0; }
DCT
UFMS
23.3 C ADEIAS
DE CARACTERES
139
Exerccios
23.1 Dada uma frase com no mximo 100 caracteres, determinar quantos caracteres espao a frase contm. Programa 23.3: Soluo do exerccios 23.1.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
#include <stdio.h> #define MAX 100 int main(void) { char frase[MAX+1]; int esp, i; printf("Informe uma frase: "); scanf("%[^\n]", frase); esp = 0; for (i = 0; frase[i] != \0; i++) if (frase[i] == ) esp++; printf("Frase tem %d espaos\n", esp); return 0; }
23.2 Dada uma cadeia de caracteres com no mximo 100 caracteres, contar a quantidade de letras minsculas, letras maisculas, dgitos, espaos e smbolos de pontuao que essa cadeia possui. 23.3 Dadas duas cadeias de caracteres cadeia1 e cadeia2 , concatenar cadeia2 no nal de cadeia1 , colocando o caracter nulo no nal da cadeia resultante. A cadeia resultante a ser mostrada deve estar armazenada em cadeia1 . Suponha que as cadeias sejam informadas com no mximo 100 caracteres. 23.4 Dada uma cadeia de caracter cadeia com no mximo 100 caracteres e um caractere c , buscar a primeira ocorrncia de c em cadeia . Se c ocorre em cadeia , mostrar a posio da primeira ocorrncia; caso contrrio, mostrar o valor -1 . 23.5 Dadas duas cadeias de caracteres cadeia1 e cadeia2 , cada uma com no mximo 100 caracteres, compar-las e devolver um valor menor que zero se cadeia1 lexicogracamente menor que cadeia2 , o valor zero se cadeia1 igual ou tem o mesmo contedo que cadeia2 , ou um valor maior que zero se cadeia1 lexicogracamente maior que cadeia2 .
DCT
UFMS
A ULA 24
M ATRIZES
Nas aulas 19, 20, 21 e 22, tivemos contato com vetores ou variveis compostas homogneas unidimensionais. No entanto, uma varivel composta homognea pode ter qualquer nmero de dimenses. A partir desta aula, aprenderemos a trabalhar com as estruturas denominadas de matrizes ou variveis compostas homogneas bidimensionais. Do mesmo modo, as matrizes so variveis compostas porque representam a composio de um conjunto de valores indivisveis, homogneas porque esses valores so de um mesmo tipo de dados e bidimensionais porque, ao contrrio dos vetores que so lineares ou tm uma nica dimenso, estas estruturas tm duas dimenses. Nesta aula aprenderemos a declarar matrizes, a declarar e inicializar simultaneamente as matrizes e tambm a usar matrizes para solucionar problemas.
140
24.1 D EFINIO ,
DECLARAO E USO
141
tipo identificador[dimenso1][dimenso2];
onde tipo um dos tipos de dados da linguagem C ou um tipo denido pelo(a) programador(a), identificador o nome da varivel matriz fornecido pelo(a) programador(a) e dimenso1 e dimenso2 determinam a quantidade de linhas e colunas, respectivamente, a serem disponibilizadas para uso na matriz. Por exemplo, a declarao a seguir
int A[20][30];
faz com que 600 clulas de memria sejam reservadas, cada uma delas podendo armazenar valores do tipo int . A referncia a cada uma dessas clulas realizada pelo identicador da matriz A e por dois ndices, o primeiro que determina a linha e o segundo que determina a coluna da matriz. Na gura 24.1 mostramos um esquema de como a matriz A disposta quando declarada desta forma.
A
0 1 2 0 1 2 28 29
18 19
Figura 24.1: Matriz A com 20 linhas e 30 colunas. Na linguagem C, a primeira linha de uma matriz tem ndice 0, a segunda linha tem ndice 1, e assim por diante. Do mesmo modo, a primeira coluna da matriz tem ndice 0, a segunda tem ndice 1 e assim por diante. Para referenciar o valor da clula da linha 0 e da coluna 3 da matriz A , devemos usar o identicador da varivel e os ndices 0 e 3 envolvidos por colchetes, ou seja, A[0][3] . Apesar de visualizarmos uma matriz na forma de uma tabela bidimensional, essa no a forma de armazenamento dessa varivel na memria. A linguagem C armazena uma matriz na memria linha a linha, como mostrado na gura 24.2 para a matriz A .
DCT
UFMS
24.2 D ECLARAO
E INICIALIZAO SIMULTNEAS
142
linha 19
memria
linha 0
linha 1
A[0][0]
A[0][29] A[1][0]
A[1][29]
A[19][0]
A[19][29]
1, 2, 2, 7,
1, 0, 2, 9,
0, 1, 1, 6,
0, 5, 1, 2,
0, 7, 2, 1,
Na declarao e inicializao acima, cada inicializador fornece valores para uma linha da matriz. A linguagem C possui algumas pequenas regras para declarar e inicializar matrizes ou variveis compostas homogneas de qualquer dimenso: se um inicializador no grande o suciente para inicializar um varivel composta homognea, ento o restante dos elementos sero inicializados com 0 (zero). Por exemplo,
inicializa as duas primeiras linhas da matriz A . As duas ltimas linhas sero inicializadas com 0 (zero); se um inicializador mais interno no longo o suciente para inicializar uma linha, ento o restante dos elementos na linha inicializado com 0 (zero). Por exemplo,
1, 2, 2, 7,
as chaves internas, que determinam as inicializaes das linhas, podem ser omitidas. Neste caso, uma vez que o compilador tenha lido elementos sucientes para preencher uma linha, ele o faz e inicia o preenchimento da prxima linha. Por exemplo,
DCT UFMS
24.3 E XEMPLO
143
1, 2, 2, 7,
1, 0, 2, 9,
0, 1, 1, 6,
0, 5, 1, 2,
0, 7, 2, 1,
2, 1, 1, 0};
24.3 Exemplo
Apresentamos a seguir o programa 24.1, que resolve o seguinte problema: Dada uma matriz real B , de 5 linhas e 10 colunas, escrever um programa que calcule o somatrio dos elementos da oitava coluna e que calcule o somatrio da terceira linha. Programa 24.1: Um programa que exemplica o uso de matrizes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
#include <stdio.h> int main(void) { int i, j; float soma_c8, soma_l3, B[5][10]; for (i = 0; i < 5; i++) { for (j = 0; j < 10; j++) { printf("Informe B[%d][%d] = ", i, j); scanf("%f", &B[i][j]); } } soma_c8 = 0.0; for (i = 0; i < 5; i++) soma_c8 = soma_c8 + B[i][7]; printf("Valor da soma da oitava coluna %4.4f\n", soma_c8); soma_l3 = 0.0; for (j = 0; j < 10; j++) soma_l3 = soma_l3 + B[2][j]; printf("Valor da soma da terceira linha %4.4f\n", soma_l3); return 0; }
Exerccios
24.1 Dadas duas matrizes de nmeros inteiros A e B , de dimenses m n, com 1 fazer um programa que calcule a matriz Cmn = A + B .
DCT
m, n
100,
UFMS
24.3 E XEMPLO
144
#include <stdio.h> #define MAX 100 int main(void) { int m, n, i, j, A[MAX][MAX], B[MAX][MAX], C[MAX][MAX]; printf("Informe as dimenses (m,n) das matrizes: "); scanf("%d%d", &m, &n); for (i = 0; i < m; i++) for (j = 0; j < n; j++) { printf("Informe A[%2d][%2d]: ", i, j); scanf("%d", &A[i][j]); } for (i = 0; i < m; i++) for (j = 0; j < n; j++) { printf("Informe B[%2d][%2d]: ", i, j); scanf("%d", &B[i][j]); } for (i = 0; i < m; i++) for (j = 0; j < n; j++) C[i][j] = A[i][j] + B[i][j]; for (i = 0; i < m; i++) { for (j = 0; j < n; j++) printf("%2d ", C[i][j]); printf("\n"); } return 0; }
24.2 Fazer um programa que dada uma matriz de nmeros reais Amn , determine At . Suponha que 1 m, n 100. 24.3 Dada uma matriz de nmeros reais A com m linhas e n colunas, 1 m, n 100, e um vetor de nmeros reais v com n elementos, determinar o produto de A por v . 24.4 Um vetor de nmeros reais x com n elementos apresentado como resultado de um sistema de equaes lineares Ax = b, cujos coecientes so representados em uma matriz de nmeros reais Amn e o lados direitos das equaes em um vetor de nmeros reais b de m elementos. Dados A, x e b, vericar se o vetor x realmente soluo do sistema Ax = b, supondo que 1 m, n 100.
DCT
UFMS
A ULA 25
E XERCCIOS
Enunciados
25.1 Dadas duas matrizes de nmeros reais Amn e Bnp , com 1 produto de A por B . 25.2 Dada uma matriz de nmeros inteiros Amn , com 1 linhas e o nmero de colunas nulas da matriz. Exemplo: Se a matriz A tem m = 4 linhas, n = 4 colunas e contedo 1 0 2 3 4 0 5 6 0 0 0 0 0 0 0 0 ento A tem 2 linhas nulas e 1 coluna nula. 25.3 Dizemos que uma matriz de nmeros inteiros Ann uma matriz de permutao se em cada linha e em cada coluna houver n 1 elementos nulos e um nico elemento 1. Exemplo: A matriz abaixo de permutao 0 1 0 0 0 0 1 0 1 0 0 0 . 0 0 0 1 Observe que 2 1 0 1 2 0 0 0 1 m, n m, n, p 100, calcular o
Dada uma matriz de nmeros inteiros Ann , , com 1 permutao. 25.4 Dada uma matriz de nmeros reais Amn , com 1 elementos repetidos em A.
145
146
#include <stdio.h> #define MAX 100 int main(void) { int m, n, p, i, j, k; float A[MAX][MAX], B[MAX][MAX], C[MAX][MAX]; printf("Informe as dimenses (m,n) da matriz A: "); scanf("%d%d", &m, &n); printf("Informe o nmero de colunas da matriz B: "); scanf("%d", &p); for (i = 0; i < m; i++) for (j = 0; j < n; j++) { printf("Informe A[%2d][%2d]: ", i, j); scanf("%f", &A[i][j]); } for (i = 0; i < n; i++) for (j = 0; j < p; j++) { printf("Informe B[%2d][%2d]: ", i, j); scanf("%f", &B[i][j]); } for (i = 0; i < m; i++) for (j = 0; j < p; j++) { C[i][j] = 0.0; for (k = 0; k < n; k++) C[i][j] = C[i][j] + A[i][k] + B[k][j]; } for (i = 0; i < m; i++) { for (j = 0; j < p; j++) printf("%2.2f ", C[i][j]); printf("\n"); } return 0; }
DCT
UFMS
A ULA 26
E XERCCIOS
Enunciados
26.1 Dizemos que uma matriz quadrada de nmeros inteiros distintos um quadrado mgico1 se a soma dos elementos de cada linha, a soma dos elementos de cada coluna e a soma dos elementos da diagonal principal e secundria so todas iguais. Exemplo: A matriz 8 0 7 4 5 6 3 10 2
um quadrado mgico. Dada uma matriz quadrada de nmeros inteiros Ann , com 1 um quadrado mgico. 26.2 n 100, vericar se A n 100.
1 3 6 10
1 4 10
1 5
(b) Imprimir as n primeiras linhas do tringulo de Pascal usando apenas um vetor. 26.3 Um jogo de palavras cruzadas pode ser representado por uma matriz Amn onde cada posio da matriz corresponde a um quadrado do jogo, sendo que 0 indica um quadrado branco e 1 indica um quadrado preto. Indicar em uma dada matriz Amn de 0 e 1, com 1 m, n 100, as posies que so incio de palavras horizontais e/ou verticais nos quadrados correspondentes (substituindo os zeros), considerando que uma palavra deve ter pelo menos duas letras. Para isso, numere consecutivamente tais posies. Exemplo:
1 2
O primeiro registro conhecido de um quadrado mgico vem da China e data do ano de 650 a.c. Descoberto em 1654 pelo matemtico francs Blaise Pascal.
147
148
#include <stdio.h> #define MAX 100 int main(void) { int n, i, j, A[MAX][MAX], soma, soma_aux, magico; printf("Informe a dimenso n da matriz: "); scanf("%d", &n); for (i = 0; i < n; i++) for (j = 0; j < n; j++) { printf("Informe A[%2d][%2d]: ", i, j); scanf("%d", &A[i][j]); } soma = 0; for (j = 0; j < n; j++) soma = soma + A[0][j]; magico = 1; for (i = 1; i < n && magico; i++) { soma_aux = 0; for (j = 0; j < n; j++) soma_aux = soma_aux + A[i][j]; if (soma_aux != soma) magico = 0; } for (j = 0; j < n && magico; j++) { soma_aux = 0; for (i = 0; i < n; i++) soma_aux = soma_aux + A[i][j]; if (soma_aux != soma) magico = 0; } soma_aux = 0; for (i = 0; i < n && magico; i++) soma_aux = soma_aux + A[i][i]; if (soma_aux != soma) magico = 0; soma_aux = 0; for (i = 0; i < n; i++) soma_aux = soma_aux + A[i][n-i-1]; if (soma_aux != soma) magico = 0; if (magico) printf("Quadrado mgico de ordem %d\n", n); else printf("Matriz no quadrado mgico\n"); return 0; }
DCT
UFMS
26.4 Os elementos aij de uma matriz de nmeros inteiros Ann representam os custos de transporte da cidade i para a cidade j . Dados uma matriz Ann , com 1 n 100, um nmero inteiro m > 0 representando a quantidade de itinerrios, um nmero k representando o nmero de cidades em cada um dos itinerrios, calcular o custo total para cada itinerrio. Exemplo: Dado 4 5 A= 2 7 1 2 1 1 2 3 1 400 3 8 2 5
m = 1, k = 8 e o itinerrio 0 3 1 3 3 2 1 0, o custo total desse itinerrio a03 + a31 + a13 + a33 + a32 + a21 + a10 = 3 + 1 + 400 + 5 + 2 + 1 + 5 = 417 . 26.5 Dados um caa-palavra, representado por uma matriz A de letras de dimenso m n, com 1 m, n 50, e uma lista de k > 0 palavras, encontrar a localizao (linha e coluna) no caa-palavras em que cada uma das palavras pode ser encontrada. a q b p o h z o t q r m c y m f c j l b s b o u a u a a g o c g y r d l t s m x j a a j z s o q g z o v u q d h l o m c u j r n a y b c o a a k d l a e t s m t k e y a k l w t d c o a e p s z h c e q k z r w u h l g
amora
Uma palavra encontrada no caa-palavras se uma seqncia ininterrupta de letras na tabela coincide com a palavra. Considere que as letras so apenas as minsculas. A busca por uma palavra deve ser feita em qualquer das oito direes no caa-palavras.
DCT UFMS
150 26.6 Considere n cidades numeradas de 0 a n 1 que esto interligadas por uma srie de estradas de mo nica, com 1 n 100. As ligaes entre as cidades so representadas pelos elementos de uma matriz quadrada Ann , cujos elementos aij assumem o valor 1 ou 0, conforme exista ou no estrada direta que saia da cidade i e chegue cidade j . Assim, os elementos da coluna j indicam as estradas que chegam cidade j . Por conveno aii = 1. A gura abaixo mostra um exemplo para n = 4.
A 0
0 1 2 3 1 1 1 0
1 1 1 0 1
2 1 1 1 1
3 0 0 0 1
(a) Dado k, determinar quantas estradas saem e quantas chegam cidade k. (b) A qual das cidades chega o maior nmero de estradas? (c) Dado k, vericar se todas as ligaes diretas entre a cidade k e outras so de mo dupla. (d) Relacionar as cidades que possuem sadas diretas para a cidade k. (e) Relacionar, se existirem: i. As cidades isoladas, isto , as que no tm ligao com nenhuma outra; ii. As cidades das quais no h sada, apesar de haver entrada; iii. As cidades das quais h sada sem haver entrada. (f) Dada uma seqncia de m inteiros cujos valores esto entre 0 e n 1, vericar se possvel realizar o roteiro correspondente. No exemplo dado, o roteiro representado pela seqncia 2 0 1 2 3, com m = 5, impossvel. (g) Dados k e p, determinar se possvel ir da cidade k para a cidade p pelas estradas existentes. Voc consegue encontrar o menor caminho entre as duas cidades?
DCT
UFMS
A ULA 27
R EGISTROS
Nas aulas 19 a 26 aprendemos a trabalhar com variveis compostas homogneas unidimensionais e bidimensionais ou os vetores e as matrizes , que permitem que um programador agrupe valores de um mesmo tipo em uma nica entidade lgica. Como mencionamos antes, a dimenso de uma varivel composta homognea no ca necessariamente restrita a uma ou duas. Isto , tambm podemos declarar e usar variveis compostas homogneas com trs ou mais dimenses. Importante relembrar tambm que, para referenciar um elemento em uma varivel composta homognea so necessrios o seu identicador e um ou mais ndices. A linguagem C dispe tambm de uma outra forma para agrupamento de dados, chamada varivel composta heterognea, registro ou estrutura1 . Nelas, diferentemente do que nas variveis compostas homogneas, podemos armazenar sob uma mesma entidade lgica valores de tipos diferentes. Alm disso, ao invs de ndices ou endereos usados nas variveis compostas homogneas para acesso a um valor, especicamos o nome de um campo para selecionar um campo particular do registro. Os registros so os objetos de estudo desta aula.
27.1 Denio
Uma varivel composta heterognea ou registro uma estrutura onde podemos armazenar valores de tipos diferentes sob uma mesma entidade lgica. Cada um desses possveis valores armazenado em um compartimento do registro denominado campo do registro, ou simplesmente campo. Um registro composto pelo seu identicador e pelos seus campos. Suponha que queremos trabalhar com um agrupamento de valores que representam uma determinada mercadoria de uma loja, cujas informaes relevantes so o cdigo do produto e seu valor. Na linguagem C, podemos ento declarar um registro com identicador produto da seguinte forma:
struct e member (of a structure) so jarges comuns na linguagem C. A traduo literal dessas palavras pode nos confundir com outros termos j usados durante o curso. Por isso, escolhemos registro e campo (do registro) como tradues para o portugus.
151
27.1 D EFINIO
152
A gura 27.1 mostra a disposio do registro produto e de seus campos na memria do computador.
produto
Figura 27.1: Representao do registro produto na memria. A declarao de um registro sempre inicia com a palavra reservada struct . Em seguida, um bloco de declaraes iniciado com o smbolo { . Depois disso, podemos declarar os campos do registro, linha aps linha. Cada linha deve conter o tipo e o identicador do campo do registro. Finalizamos ento as declaraes dos campos do registro com o smbolo } e, depois dele, realizamos de fato a declarao do registro, digitando o seu identicador. A varivel do tipo registro com identicador horario declarada acima contm trs campos, dois do tipo inteiro com identicadores horas e quant , e outro do tipo ponto utuante valor . Podemos declarar outras variveis do tipo registro com os mesmos campos da varivel produto da seguinte forma:
struct { int codigo; int quant; float valor; } produto, estoque, baixa;
Na linguagem C, uma forma alternativa de declarao dos campos de um registro declarar campos de um mesmo tipo em uma nica linha de cdigo. Por exemplo,
struct { char sala, turma; int horas_inicio, minutos_inicio, horas_fim, minutos_fim; float dimensoes_sala; } aula;
declara um registro com identicador aula com seis campos, dois campos do tipo char , quatro outros campos do tipo int e um campo do tipo float . Apesar dessa declarao estar correta e de economizar algumas linhas de cdigo, sempre optamos pela declarao dos campos separadamente, linha a linha, para que os campos quem bem destacados e, assim, facilitem sua identicao rpida no cdigo. Dessa forma, em geral optamos pela seguinte declarao do registro aula :
DCT
UFMS
27.1 D EFINIO
153
struct { char sala; char turma; int horas_inicio; int minutos_inicio; int horas_fim; int minutos_fim; float dimensoes_sala; } aula;
Diferentemente da atribuio de um valor a uma varivel ou a um compartimento de uma varivel composta homognea, a atribuio de um valor a um campo de uma varivel do tipo registro realizada atravs do acesso a esse campo, especicando o identicador do registro, um ponto e o identicador do campo. Por exemplo, para atribuir os valores 12, 5 e 34.5 aos campos codigo , quant e valor , respectivamente, da varivel do tipo registro produto declarada anteriormente, devemos fazer como abaixo:
Observe acima que, quando referenciamos um campo de uma varivel do tipo registro, no so permitidos espaos entre o identicador do registro, o ponto e o identicador do campo. Tambm podemos usar o valor de um campo de um registro em quaisquer expresses. Por exemplo, a expresso relacional abaixo correta:
Declaraes de registros diferentes podem conter campos com mesmo identicador. Por exemplo,
struct { char tipo; char fatorRH; int idade; float altura; } coleta;
DCT UFMS
27.2 D ECLARAO
E INICIALIZAO SIMULTNEAS
154
so declaraes vlidas na linguagem C. O acesso aos campos dessas variveis se diferencia justamente pelo identicador dos registros. Isto , a partir das declaraes dos registros coleta e certidao , as atribuies abaixo esto corretas:
struct { int codigo; int quant; float valor; } produto = {1, 5, 34.5};
Neste caso, o campo codigo inicializado com o valor 1 , o campo quant inicializado com o valor 5 e o campo valor com 34.5 . Como mencionado, as regras de declarao e inicializao simultneas para registros so equivalentes quelas para vetores. Assim,
far a inicializao dos campos codigo e quant com 0 e do campo valor com 0.0 .
DCT UFMS
27.3 O PERAES
SOBRE REGISTROS
155
A = B;
para tentar copiar os valores do vetor B no vetor A . O correto, neste caso, fazer a cpia elemento a elemento de uma varivel para outra, como mostrado a seguir:
supondo que n a dimenso dos vetores A e B . Quando tratamos de registros, no entanto, podemos fazer uma atribuio direta e realizar a cpia de todos os seus campos nessa nica atribuio. O trecho de cdigo a seguir mostra um exemplo com a declarao de dois registros com mesmos campos, a atribuio de valores ao primeiro registro e uma cpia completa de todos os campos do primeiro registro para o segundo:
struct { char tipo; int codigo; int quant; float valor; } mercadoria1, mercadoria2; mercadoria1.tipo = A; mercadoria1.codigo = 10029; mercadoria1.quant = 62; mercatoria1.valor = 10.32 * TAXA + 0.53; mercadoria2 = mercadoria1;
DCT
UFMS
27.4 E XEMPLO
156
27.4 Exemplo
O programa 27.1 recebe um horrio no formato de horas, minutos e segundos, com as horas no intervalo de 0 a 23, e atualiza este horrio em um segundo. Programa 27.1: Exemplo do uso de registros.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
#include <stdio.h> int main(void) { struct { int horas; int minutos; int segundos; } agora, prox; printf("Informe o horrio atual (hh:mm:ss): "); scanf("%d:%d:%d", &agora.horas, &agora.minutos, &agora.segundos); prox = agora; prox.segundos++; if (prox.segundos == 60) { prox.segundos = 0; prox.minutos++; if (prox.minutos == 60) { prox.minutos = 0; prox.horas++; if (prox.horas == 24) prox.horas = 0; } } printf("Prximo horrio %02d:%02d:%02d\n", prox.horas, prox.minutos, prox.segundos); return 0; }
Exerccios
27.1 Dada uma data, escreva um programa que mostre a prxima data, isto , a data que representa o dia seguinte data fornecida. No esquea dos anos bissextos. Lembre-se que um ano bissexto se divisvel por 400 ou, em caso negativo, se divisvel por 4 mas no por 100. 27.2 Dados dois horrios de um mesmo dia expressos no formato hh:mm:ss, calcule o tempo decorrido entre estes dois horrios, apresentando o resultado no mesmo formato. 27.3 Dadas duas datas, calcule o nmero de dias decorridos entre estas duas datas.
DCT UFMS
27.4 E XEMPLO
157
#include <stdio.h> int main(void) { struct { int dia; int mes; int ano; } data, prox; scanf("%d/%d/%d", &data.dia, &data.mes, &data.ano); prox = data; prox.dia++; if (prox.dia > 31 || (prox.dia == 31 && (prox.mes == 4 || prox.mes == 6 || prox.mes == 9 || prox.mes == 11)) || (prox.dia >= 30 && prox.mes == 2) || (prox.dia == 29 && prox.mes == 2 && (prox.ano % 400 != 0 && prox.ano % 4 != 0)) ) { prox.dia = 1; prox.mes++; if (prox.mes > 12) { prox.mes = 1; prox.ano++; } } printf("%02d/%02d/%02d\n", prox.dia, prox.mes, prox.ano); return 0; }
Uma maneira provavelmente mais simples de computar essa diferena usar a frmula 27.1 para calcular um nmero de dias N baseado em uma data: N= onde f (ano, ms) = e g(ms) = 1461 f (ano, ms) 153 g(ms) + + dia 4 5 ano 1, se ms 2, ano, caso contrrio (27.1)
Podemos calcular o valor N1 para a primeira data informada, o valor N2 para a segunda data informada e a diferena N2 N1 o nmero de dias decorridos entre estas duas datas informadas.
DCT
UFMS
27.4 E XEMPLO 27.4 Seja N computado como na equao 27.1. Ento, o valor D = (N 621049) mod 7
158
um nmero entre 0 e 6 que representa os dias da semana, de domingo a sbado. Por exemplo, para a data de 21/06/2007 temos N= 1461 f (2007, 6) 153 g(6) + + 21 4 5 153 7 1461 2007 + + 21 = 4 5 = 733056 + 214 + 21 = 733291 e ento D = (733291 621049) mod 7 = 112242 mod 7 =4. Dada uma data fornecida pelo usurio no formato dd/mm/aaaa, determine o dia da semana para esta data.
DCT
UFMS
A ULA 28
V ETORES ,
MATRIZES E REGISTROS
Nesta aula, vamos trabalhar com uma extenso natural do uso de registros, declarando e usando variveis compostas homogneas de registros como, por exemplo, vetores de registros ou matrizes de registros. Por outro lado, estudaremos tambm registros contendo variveis compostas homogneas como campos. Veremos que combinaes dessas declaraes tambm podem ser usadas de modo a representar e organizar os dados em memria e solucionar problemas.
159
28.1 V ETORES
DE REGISTROS
160
O vetor cronometro declarado anteriormente contm 10 compartimentos de memria, sendo que cada um deles do tipo registro. Cada registro contm, por sua vez, os campos horas , minutos e segundos , do tipo inteiro. J a matriz com identicador agenda uma matriz contendo 10 linhas e 30 colunas, onde cada compartimento contm um registro com os mesmos campos do tipo inteiro horas , minutos e segundos . Essas duas variveis compostas homogneas tambm poderiam ter sido declaradas em conjunto, como apresentado a seguir:
A declarao do vetor cronometro tem um efeito na memria que pode ser ilustrado como na gura 28.1.
0
cronometro
Figura 28.1: Efeito da declarao do vetor cronometro na memria. Atribuies de valores do tipo inteiro aos campos do registro do primeiro compartimento deste vetor cronometro podem ser feitas da seguinte forma:
Alm disso, como j mencionamos na aula 27, podemos fazer a atribuio direta de registros para registros. Assim, por exemplo, se declaramos as variveis cronometro e aux como abaixo:
DCT
UFMS
28.1 V ETORES
DE REGISTROS
161
ento as atribuies a seguir so vlidas e realizam a troca dos contedos das posies i e j do vetor de registros cronometro :
importante observar novamente que todos os campos de um registro so atualizados automaticamente quando da atribuio de um registro a outro registro, no havendo a necessidade da atualizao campo a campo. Ou seja, podemos fazer:
cronometro[i] = cronometro[j];
ao invs de
As duas formas acima esto corretas, apesar da primeira forma ser mais prtica e direta. Nesse contexto, considere o seguinte problema: Dado um nmero inteiro n, com 1 n 100, e n medidas de tempo dadas em horas, minutos e segundos, distintas duas a duas, ordenar essas medidas de tempo em ordem crescente. O programa 28.1 soluciona o problema acima usando o mtodo de ordenao das trocas sucessivas ou mtodo da bolha.
DCT
UFMS
28.1 V ETORES
DE REGISTROS
162
#include <stdio.h> int main(void) { int i, j, n; struct { int horas; int minutos; int segundos; } cron[100], aux; printf("Informe a quantidade de medidas de tempo: "); scanf("%d", &n); printf("\n"); for (i = 0; i < n; i++) { printf("Informe uma medida de tempo (hh:mm:ss): "); scanf("%d:%d:%d", &cron[i].horas, &cron[i].minutos, &cron[i].segundos); } for (i = n-1; i > 0; i--) for (j = 0; j < i; j++) if (cron[j].horas > cron[j+1].horas) { aux = cron[j]; cron[j] = cron[j+1]; cronometro[j+1] = aux; } else if (cron[j].horas == cron[j+1].horas) if (cron[j].minutos > cron[j+1].minutos) { aux = cron[j]; cron[j] = cron[j+1]; cron[j+1] = aux; } else if (cron[j].minutos == cron[j+1].minutos) if (cron[j].segundos > cron[j+1].segundos) { aux = cron[j]; cron[j] = cron[j+1]; cron[j+1] = aux; } printf("\nHorrios em ordem crescente\n"); for (i = 0; i < n; i++) printf("%d:%d:%d\n", cron[i].horas, cron[i].minutos, cron[i].segundos); return 0; }
DCT
UFMS
28.2 R EGISTROS
163
A declarao do registro mes permite o armazenamento de um valor do tipo inteiro no campo dias , que pode representar, por exemplo, a quantidade de dias de um ms, e de trs valores do tipo caracter no campo vetor nome , que podem representar os trs primeiros caracteres do nome de um ms do ano. Assim, se declaramos as variveis mes e aux como a seguir:
Os efeitos da declarao da varivel mes na memria e da atribuio de valores acima podem ser vistos na gura 28.2. importante salientar mais uma vez que as regras para cpias de registros permanecem as mesmas, mesmo que um campo de um desses registros seja uma varivel composta. Assim, a cpia abaixo perfeitamente vlida:
aux = mes;
DCT
UFMS
28.2 R EGISTROS
164
Figura 28.2: Varivel mes na memria. Suponha agora que temos o seguinte problema. Dadas duas descries de tarefas e seus horrios de incio no formato hh:mm:ss, escreva um programa que verica qual das duas tarefas ser iniciada antes. Considere que a descrio de uma tarefa tenha no mximo 50 caracteres. Uma soluo para esse problema apresentada no programa 28.2. Programa 28.2: Exemplo do uso de um vetor como campo de um registro.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
#include <stdio.h> int main(void) { int tempo1, tempo2; struct { int horas; int minutos; int segundos; char descricao[51]; } t1, t2; printf("Informe a descrio da primeira atividade: "); scanf("%[^\n]", t1.descricao); printf("Informe o horrio de incio dessa atividade (hh:mm:ss): "); scanf("%d:%d:%d", &t1.horas, &t1.minutos, &t1.segundos); printf("Informe a descrio da segunda atividade: "); scanf(" %[^\n]", t2.descricao); printf("Informe o horrio de incio dessa atividade (hh:mm:ss): "); scanf("%d:%d:%d", &t2.horas, &t2.minutos, &t2.segundos); tempo1 = t1.horas * 3600 + t1.minutos * 60 + t1.segundos; tempo2 = t2.horas * 3600 + t2.minutos * 60 + t2.segundos; if (tempo1 <= tempo2) printf("%s ser realizada antes de %s\n", t1.descricao, t2.descricao); else printf("%s ser realizada antes de %s\n", t2.descricao, t1.descricao); return 0; }
DCT
UFMS
28.2 R EGISTROS
165
Exerccios
28.1 Dadas n datas, com 1 n mais prxima data d. 100, e uma data de referncia d, verique qual das n datas
Podemos usar a frmula do exerccio 27.3 para solucionar esse exerccio mais facilmente. 28.2 Dadas trs chas de produtos de um supermercado, contendo as informaes de seu cdigo, sua descrio com at 50 caracteres e seu preo unitrio, orden-las em ordem alfabtica de seus nomes. 28.3 Dados um nmero inteiro n > 1 e mais n chas de doadores de um banco de sangue, contendo o cdigo do doador, seu nome, seu tipo sangneo e seu fator Rhesus, escreva um programa que lista os doadores do banco das seguintes formas: (i) em ordem crescente de cdigos de doadores; (ii) em ordem alfabtica de nomes de doadores e (iii) em ordem alfabtica de tipos sangneos e fatores Rhesus.
DCT
UFMS
28.2 R EGISTROS
166
#include <stdio.h> #define MAX 100 int main(void) { int dif[MAX], menor; struct { int dia; int mes; int ano; } d, data[MAX]; printf("Informe uma data de referncia (dd/mm/aa): "); scanf("%d/%d/%d", &d.dia, &d.mes, &d.ano); scanf("%d", &n); printf("Informe a quantidade de datas: "); scanf("%d", &n); for (i = 0; i < n; i++) { printf("[%03d] Informe uma data (dd/mm/aa): ", i+1); scanf("%d/%d/%d", &data[i].dia, &data[i].mes, &data[i].ano); } if (d.mes <= 2) N1 = (1461*(d.ano-1))/4 + ((153*d.mes+13)/5) + d.dia; else N1 = (1461*d.ano)/4 + ((153*d.mes+1)/5) + d.dia; for (i = 0; i < n; i++) { if (data[i].mes <= 2) N2 = (1461*(data[i].ano-1))/4 + ((153*data[i].mes+13)/5) + data[i].dia; else N2 = (1461*data[i].ano)/4 + ((153*data[i].mes+1)/5) + data[i].dia; if (N1 >= N2) dif[i] = N1 - N2; else dif[i] = N2 - N1; } menor = 0; for (i = 1; i < n; i++) if (dif[i] < dif[menor]) menor = i; printf("Data mais prxima de %2d/%2d/%2d %2d/%2d/%d\n", d.dia, d.mes, d.ano, data[menor].dia, data[menor].mes, data[menor].ano); return 0; }
DCT
UFMS
A ULA 29
R EGISTROS
COM REGISTROS
Assim como na aula 28, a aula de hoje tambm trata da declarao de registros mais complexos, mas agora seus campos podem ser variveis no somente de tipos bsicos, de tipos denidos pelo usurio, ou ainda variveis compostas homogneas, mas tambm de variveis compostas heterogneas ou registros. Uma varivel que um registro e que, por sua vez, contm como campos um ou mais registros, fornece ao() programador(a) uma liberdade e exibilidade na declarao de qualquer estrutura para armazenamento de informaes que lhe seja necessria na soluo de um problema computacional, especialmente daqueles problemas mais complexos.
struct { int rga; char nome[51]; struct { int dia; int mes; int ano; } nascimento; } estudante;
29.2 E XEMPLO
168
rga
nome
nasc
48
49
Figura 29.1: Efeitos da declarao do registro estudante na memria. Observe que a varivel estudante um registro que mistura campos de um tipo bsico o campo rga que do tipo inteiro , um campo que um vetor de um tipo bsico o campo nome do tipo vetor de caracteres e um campo do tipo registro o campo nascimento do tipo registro, contendo, por sua vez, trs campos do tipo inteiro: os campos dia , mes e ano . Um exemplo do uso do registro estudante e de seus campos dado a seguir atravs de atribuies de valores a cada um de seus campos:
estudante.rga = 200790111; estudante.nome[0] = J; estudante.nome[1] = o; estudante.nome[2] = s; estudante.nome[3] = e; estudante.nome[4] = \0; estudante.nasc.dia = 22; estudante.nasc.mes = 2; estudante.nasc.ano = 1988;
29.2 Exemplo
O programa 29.1 implementa uma agenda de telefones simples, ilustrando um exemplo de uso de um vetor que contm registros, onde cada registro contm tambm um registro como campo. A agenda contm trs informaes para cada amigo(a) includo(a): nome, telefone e data de aniversrio. Alm de armazenar essa agenda na memria, em um vetor de registros, o programa ainda faz com que uma data seja fornecida pelo(a) usurio(a) e todas as pessoas que fazem aniversrio nessa data so mostradas na sada. Podemos destacar novamente, assim como zemos nas aulas 27 e 28, que registros podem ser atribudos automaticamente para registros, no havendo necessidade de faz-los campo a campo. Por exemplo, se temos declarados os registros:
DCT UFMS
29.2 E XEMPLO
169
#include <stdio.h> int main(void) { int i, n; struct { char nome[51]; int telefone; struct { int dia; int mes; int ano; } aniver; } agenda[100]; struct { int dia; int mes; } data; printf("Informe a quantidade de amigos: "); scanf("%d", &n); for(i = 0; i < n; i++) { printf("\nAmigo(a): %3d\n", i+1); printf(" Nome : "); scanf(" %[^\n]", agenda[i].nome); printf(" Telefone : "); scanf("%d", &agenda[i].telefone); printf(" Aniversrio: "); scanf("%d/%d/%d", &agenda[i].aniver.dia, &agenda[i].aniver.mes, &agenda[i].aniver.ano); } printf("\nInforme uma data (dd/mm): "); scanf("%d/%d", &data.dia, &data.mes); i = 0; while (i < n) { if (agenda[i].aniver.dia == data.dia && agenda[i].aniver.mes == data.mes) printf("%-50s %8d\n", agenda[i].nome, agenda[i].telefone); i++; } return 0; }
DCT
UFMS
29.2 E XEMPLO
170
struct { int rga; char nome[51]; struct { int dia; int mes; int ano; } nascimento; } estudante1, estudante2, aux;
ento, as atribuies a seguir so perfeitamente vlidas e realizam corretamente a troca de contedos dos registros estudante1 e estudante2 :
Exerccios
29.1 Suponha que em um determinado galpo estejam armazenados os materiais de construo de uma loja que vende tais materiais. Este galpo quadrado e mede 20 20 = 400m2 e a cada 22 = 4m2 h uma certa quantidade de um material armazenado. O encarregado do setor tem uma tabela de 10 linhas por 10 colunas, representando o galpo, contendo, em cada clula, o cdigo do material, sua descrio e sua quantidade. O cdigo do material um nmero inteiro, a descrio o material contm no mximo 20 caracteres e a quantidade do material um nmero de ponto utuante. Escreva um programa que receba as informaes armazenadas na tabela do encarrerado e liste cada material e a sua quantidade disponvel no galpo. Observe que um mesmo material pode encontrar-se em mais que um local no galpo. 29.2 Escreva um programa que receba o nome, o telefone e a data de nascimento de n pessoas, com 1 n 100, e implemente uma agenda telefnica com duas listagens possveis: (i) uma lista dos nomes e telefones das pessoas em ordem alfabtica de nomes e (ii) uma lista dos nomes e telefones das pessoas em ordem de datas de aniversrios das pessoas.
DCT
UFMS
29.2 E XEMPLO
171
#include <stdio.h> #define MAX 10 int main(void) { int i, j, k, l, m; float soma; struct { int codigo; char descricao[21]; float quant; int marca; } galpao[MAX][MAX], resumo[MAX*MAX]; for(i = 0; i < MAX; i++) for (j = 0; j < MAX; j++) { scanf("%d %[\^n]%f", &galpao[i][j].codigo, galpao[i][j].descricao, &galpao[i][j].quant); galpao[i][j].marca = 0; } for (m = 0, i = 0; i < MAX; i++) for (j = 0; j < MAX; j++) if (!galpao[i][j].marca) { if (j < MAX - 1) { k = i; l = j + 1; } else { k = i + 1; l = 0; } soma = galpao[i][j].quant; for ( ; k < MAX; k++, l = 0) for ( ; l < MAX; l++) if (galpao[k][l].codigo == galpao[i][j].codigo) { soma = soma + galpao[k][l].quant; galpao[k][l].marca = 1; } resumo[m] = galpao[i][j]; resumo[m].quant = soma; m++; galpao[i][j].marca = 1; } for (i = 0; i < m; i++) printf("%d %s %5.2f\n", resumo[i].codigo, resumo[i].descricao, resumo[i].quant); return 0; }
DCT
UFMS
A ULA 30
U NIES
E ENUMERAES
Complementando o contedo apresentado at aqui sobre variveis compostas, a linguagem C ainda oferece dois tipos delas: as unies e as enumeraes. Uma unio um registro que armazena apenas um dos valores de seus campos e, portanto, apresenta economia de espao. Uma enumerao ajuda o(a) programador(a) a declarar e usar uma varivel que representa intuitivamente uma seqncia de nmeros naturais.
30.1 Unies
Como os registros, as unies tambm so compostas por um ou mais campos, possivelmente de tipos diferentes. No entanto, o compilador aloca espao suciente apenas para o maior dos campos de uma unio. Esses campos compartilham ento o mesmo espao na memria. Dessa forma, a atribuio de um valor a um dos campos da unio tambm altera os valores dos outros campos. Vamos declarar a unio u com dois campos, um do tipo caractere com sinal e outro do tipo inteiro com sinal, como abaixo:
Observe que a declarao de uma unio idntica declarao de um registro que aprendemos nas aulas 27, 28 e 29, a menos da palavra reservada union que substitui a palavra reservada struct . Por exemplo, podemos declarar o registro r com os mesmos dois campos da unio u como a seguir:
172
30.1 U NIES
173
A diferena entre a unio u e o registro r concentra-se apenas no fato que os campos de u esto localizados no mesmo endereo de memria enquanto que os campos de r esto localizados em endereos diferentes. Considerando que um caractere ocupa um byte na memria e um nmero inteiro ocupa quatro bytes, a gura 30.1 apresenta uma ilustrao do que ocorre na memria na declarao das variveis u e r .
c r i
c u
Figura 30.1: Unio e registro representados na memria. No registro r , os campos c e i ocupam diferentes posies da memria. Assim, o total de memria necessrio para armazenamento do registro r na memria de cinco bytes. Na unio u , os campos c e i compartilham a mesma posio da memria isto , os campos c e i de u tm o mesmo endereo de memria e, portanto, o total de memria necessrio para armazenamento de u de quatro bytes. Campos de uma unio so acessados da mesma forma que os campos de um registro. Por exemplo, podemos fazer a atribuio a seguir:
u.c = a;
Tambm podemos fazer a atribuio de um nmero inteiro para o outro campo da unio como apresentado abaixo:
u.i = 3894;
Como o compilador superpe valores nos campos de uma unio, a alterao de um dos campos implica na alterao de qualquer valor previamente armazenado nos outros campos. Assim, se armazenamos um valor no campo u.c , qualquer valor previamente armazenado no campo u.i ser perdido. Do mesmo modo, alterar o valor do campo u.i altera o valor do campo u.c . Assim, podemos pensar na unio u como um local na memria para armazenar um caractere em u.c OU ou um nmero em u.i , mas no ambos. O registro r pode armazenar valores em r.c E em r.i .
DCT
UFMS
30.1 U NIES
174
Como com registros, unies podem ser copiadas diretamente usando o comando de atribuio = , sem a necessidade de faz-lo campo a campo. Declaraes e inicializaes tambm so feitas similarmente. No entanto, somente o primeiro campo de uma unio pode ter um valor atribudo no inicializador. Por exemplo, podemos fazer
Usamos unies freqentemente como uma forma para economizar espao em registros. Tambm, usamos unies quando queremos criar estruturas de armazenamento de dados que contenham uma mistura de diferentes tipos de dados. Como um exemplo do segundo caso, suponha que temos uma coleo de nmeros inteiros e de ponto utuante que queremos armazenar em um nico vetor. Como os elementos de um vetor tm de ser do mesmo tipo, devemos usar unies para implementar um vetor com essa caracterstica. A declarao de um vetor como esse dada a seguir:
Cada compartimento do vetor pode armazenar um valor do tipo int ou um valor do tipo double , o que possibilita armazenar uma mistura de valores do tipo int e double no vetor . Por exemplo, para as atribuies abaixo atribuem nmeros inteiros e nmeros de ponto utuante para as posies 0 e 1 do vetor :
Suponha que temos ento o seguinte problema que precisamos construir um programa para solucion-lo: Dada uma seqncia n de nmeros, inteiros e reais, com 1 n 100, realizar a adio de todos os nmeros inteiros e o produto de todos os nmeros reais. O programa 30.1 mostra um exemplo do uso de um vetor de registros onde um de seus campos uma unio.
DCT
UFMS
30.1 U NIES
175
#include <stdio.h> #define TIPO_INT 0 #define TIPO_DOUBLE 1 #define MAX 100 int main(void) { int i, n, soma, inteiro, real; double produto; struct { int tipo; union { int i; double d; } u; } numero[MAX]; printf("Informe n: "); scanf("%d", &n); for (i = 0; i < n; i++) { printf("Informe o tipo do nmero (0: inteiro, 1: real): "); scanf("%d", &numero[i].tipo); printf("Informe o nmero: "); if (numero[i].tipo == TIPO_INT) scanf("%d", &numero[i].u.i); else scanf("%lf", &numero[i].u.d); } soma = 0; produto = 1.0; inteiro = 0; real = 0; for (i = 0; i < n; i++) if (numero[i].tipo == TIPO_INT) { soma = soma + numero[i].u.i; inteiro = 1; } else { produto = produto * numero[i].u.d; real = 1; } if (inteiro) printf("Soma dos nmeros inteiros: %d\n", soma); else printf("No h nmeros inteiros na entrada\n"); if (real) printf("Produto dos nmeros reais: %g\n", produto); else printf("No h nmeros reais na entrada\n"); return 0; }
DCT UFMS
30.2 E NUMERAES
176
30.2 Enumeraes
Muitas vezes precisamos de variveis que contero, durante a execuo de um programa, somente um pequeno conjunto de valores. Por exemplo, variveis lgicas ou booleanas devero conter somente dois valores possveis: verdadeiro e falso. Uma varivel que armazena os naipes das cartas de um baralho dever conter apenas quatro valores potenciais: paus, copas, espadas e ouros. Uma maneira natural de tratar esses valores atravs da declarao de uma varivel do tipo inteiro que pode armazenar valores que representam esses naipes. Por exemplo, 0 representa o naipe paus, 1 o naipe copas e assim por diante. Assim, podemos por exemplo declarar uma varivel naipe e atribuir valores a essa varivel da seguinte forma:
int n; n = 0;
Apesar dessa tcnica funcionar, algum que precisa compreender esse programa pode no ser capaz de saber que a varivel n pode assumir apenas quatro valores durante a execuo do programa e o signicado do valor 0 no imediatamente aparente. Uma estratgia melhor que a anterior o uso de macros para denir um tipo naipe e tambm para denir nomes para os diversos naipes existentes. Ento, podemos fazer:
int 0 1 2 3
NAIPE n; n = PAUS;
Apesar de melhor, essa estratgia ainda possui restries. Por exemplo, algum que necessita compreender o programa no tem a viso imediata que as macros representam valores do mesmo tipo. Alm disso, se o nmero de valores possveis um pouco maior, a denio de uma macro para cada valor pode se tornar uma tarefa tediosa. Por m, os identicadores das macros sero removidos pelo pr-processador e substitudos pelos seus valores respectivos e isso signica que no estaro disponveis para a fase de depurao do programa. Depurao uma atividade muito til para programao a medida que nossos programas cam maiores e mais complexos, como veremos na aula 31.
DCT UFMS
30.2 E NUMERAES
177
A linguagem C possui um tipo especial especco para variveis que armazenam um conjunto pequeno de possveis valores. Um tipo enumerado um tipo cujos valores so listados ou enumerados pelo(a) programador(a) que deve criar um nome, chamado de uma constante da enumerao, para cada um dos valores. O exemplo a seguir enumera os valores PAUS , COPAS , ESPADAS e OUROS que podem ser atribudos s variveis n1 e n2 :
Apesar de terem pouco em comum com registros e unies, as enumeraes so declaradas de modo semelhante. Entretanto, diferentemente dos campos dos registros e unies, os nomes das constantes da enumerao devem ser diferentes de outros nomes de outras enumeraes declaradas. A linguagem C trata variveis que so enumeraes e constantes da enumerao como nmeros inteiros. Por padro, o compilador atribui os inteiros 0, 1, 2, . . . para as constantes da enumerao na declarao de uma varivel qualquer do tipo enumerao. No exemplo acima, PAUS , COPAS , ESPADAS e OUROS representam os valores 0, 1, 2 e 3, respectivamente. Podemos escolher valores diferentes dos acima para as constantes da enumerao. Por exemplo, podemos fazer a seguinte declarao:
Os valores das constantes da enumerao podem ser valores inteiros arbitrrios, listados sem um ordem particular, como por exemplo:
Se nenhum valor especicado para uma constante da enumerao, o valor atribudo constante um maior que o valor da constante anterior. A primeira constante da enumerao tem o valor padro 0 (zero). Na enumerao a seguir, PRETO tem o valor 0, CINZA_CLARO tem o valor 7, CINZA_ESCURO temo o valor 8 e BRANCO tem o valor 15:
Como as constantes de uma enumerao so, na verdade, nmeros inteiros, a linguagem C permite que um(a) programador(a) use-as em expresses com inteiros. Por exemplo, o trecho de cdigo a seguir vlido em um programa:
DCT UFMS
30.2 E NUMERAES
178
Apesar da convenincia de podermos usar uma constante de uma enumerao como um nmero inteiro, perigoso usar um nmero inteiro como um valor de uma enumerao. Por exemplo, podemos acidentalmente armazenar o nmero 4, que no corresponde a qualquer naipe, na varivel n . Vejamos o programa 30.2 com um exemplo do uso de enumeraes, onde contamos o nmero de cartas de cada naipe de um conjunto de cartas fornecido como entrada. Uma entrada uma cadeia de caracteres, dada como 4pAp2c1o7e, onde p signica o naipe de paus, c copas, e espadas e o ouros.
Exerccios
30.1 Escreva um programa que receba uma coleo de n nmeros, com 1 n 100, que podem ser inteiros pequenos de 1 (um) byte, inteiros maiores de 4 (quatro) bytes ou nmeros reais, e receba ainda um nmero x e verique se x pertence a esse conjunto. Use uma forma de armazenamento que minimize a quantidade de memria utilizada. Use um vetor de registros tal que cada clula contm um campo que uma enumerao, para indicar o tipo do nmero armazenado nessa clula, e um campo que uma unio, para armazenar um nmero. 30.2 Dados os nmeros inteiros m e n e uma matriz A de nmeros inteiros, com 1 m, n 100, calcular a quantidade de linhas que contm o elemento 0 (zero) e a quantidade de colunas. Use uma varivel indicadora de passagem, lgica, declarada como uma enumerao.
DCT
UFMS
30.2 E NUMERAES
179
#include <stdio.h> int main(void) { char entrada[109]; int i, j, conta[4] = {0}; struct { char valor[2]; enum {PAUS, COPAS, ESPADAS, OUROS} naipe; } mao[52]; printf("Informe uma mo: "); scanf("%s", entrada); for (i = 0, j = 0; entrada[i]; j++) { mao[j].valor[0] = entrada[i]; i++; if (entrada[i] == 0) { mao[j].valor[1] = entrada[i]; i++; } switch (entrada[i]) { case p: mao[j].naipe = PAUS; break; case c: mao[j].naipe = COPAS; break; case e: mao[j].naipe = ESPADAS; break; case o: mao[j].naipe = OUROS; break; default: break; } i++; } for (j--; j >= 0; j--) conta[mao[j].naipe]++; printf("Cartas:\n"); printf(" %d de paus\n", conta[PAUS]); printf(" %d de copas\n", conta[COPAS]); printf(" %d de espadas\n", conta[ESPADAS]); printf(" %d de ouros\n", conta[OUROS]); return 0; }
DCT
UFMS
A ULA 31
D EPURAO
DE PROGRAMAS
medida que temos resolvido problemas maiores e mais complexos, natural e conseqentemente nossos programas tm ganhado complexidade e extenso. Uso correto de estruturas de programao, identicadores de variveis signicativos, indentao, comentrios, tudo isso tem sido feito na tentativa de escrever programas corretamente. Ou seja, nosso desejo sempre escrever programas ecientes, coesos e corretos. Infelizmente, nem sempre isso possvel. Por falta de ateno, de experincia, de disciplina, ou mesmo pela complexidade do problema que precisamos resolver, no incomum o desenvolvimento de um programa que contm erros. Nesta aula, aprenderemos a usar um poderoso depurador de programas interativo, chamado GDB, como aliado na construo de programas corretos. Veremos algumas de suas principais caractersticas, talvez as mais teis. Interessados(as) em mais informaes e caractersticas avanadas desta ferramenta devem consultar a pgina e o manual da mesma.
Richard Matthew Stallman (), nascido nos Estados Unidos, fsico, ativista poltico e ativista de software.
180
31.2 P RIMEIRO
CONTATO
181
GDB um software livre, protegido pela Licena Pblica Geral (GPL) da GNU . A GPL d a seu usurio a liberdade para copiar ou adaptar um programa licenciado, mas qualquer pessoa que faz uma cpia tambm deve ter a liberdade de modicar aquela cpia o que signica que deve ter acesso ao cdigo fonte , e a liberdade de distribuir essas cpias. Empresas de software tpicas usam o copyright para limitar a liberdade dos usurios; a Fundao para o Software Livre (Free Software Foundation) usa a GPL para preservar essas liberdades. Fundamentalmente, a Licena Pblica Geral (GPL) uma licena que diz que todas as pessoas tm essas liberdades e que ningum pode restringi-las.
Para executar um programa com auxlio do depurador GDB, o programa fonte na linguagem C deve ser compilado com o compilador GCC usando a diretiva de compilao ou opo -g . Essa diretiva de compilao faz com que o compilador adicione informaes extras no programa executvel que sero usadas pelo GDB.
$ gdb GNU gdb 6.8-debian Copyright (C) 2008 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i486-linux-gnu". (gdb)
Em geral, quando queremos depurar um programa, digitamos o comando gdb programa em uma janela de comandos, onde programa um programa executvel compilado e gerado pelo compilador gcc usando a diretiva de compilao -g . Dessa forma,
$ gdb ./consecutivos GNU gdb 6.8-debian Copyright (C) 2008 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i486-linux-gnu"... (gdb)
DCT
UFMS
31.3 S INTAXE
182
faz a chamada do depurador GDB com a carga do programa executvel consecutivos , que foi previamente gerado pelo processo de compilao, usando o compilador GCC com a diretiva de compilao -g . Para sair do seguir:
GDB
(gdb) quit $
List of classes of commands: aliases -- Aliases of other commands breakpoints -- Making program stop at certain points data -- Examining data files -- Specifying and examining files internals -- Maintenance commands obscure -- Obscure features running -- Running the program stack -- Examining the stack status -- Status inquiries support -- Support facilities tracepoints -- Tracing of program execution without stopping the program user-defined -- User-defined commands Type "help" followed by a class name for a list of commands in that class. Type "help all" for the list of all commands. Type "help" followed by command name for full documentation. Type "apropos word" to search for commands related to "word". Command name abbreviations are allowed if unambiguous. (gdb)
DCT
UFMS
31.4 P ONTOS
DE PARADA
183
Usando o nome de uma das classes gerais de ajuda como um argumento do comando
help , podemos obter uma lista dos comandos desta classe. Por exemplo,
(gdb) help status Status inquiries. List of commands: info -- Generic command for showing things about the program being debugged macro -- Prefix for commands dealing with C preprocessor macros show -- Generic command for showing things about the debugger Type "help" followed by command name for full documentation. Command name abbreviations are allowed if unambiguous. (gdb)
Tambm podemos usar o comando help diretamente com o nome de um comando que queremos ajuda. Por exemplo,
GDB
run , que pode ser abreviado por r . Este programa executvel deve ter sido usado como um
parmetro na chamada do depurador ou ento deve ser carregado no GDB usando o comando file ou exec-file .
DCT
UFMS
31.4 P ONTOS
DE PARADA
184
GDB existem trs tipos de pontos de parada: breakpoints, watchpoints e catchpoints. Nesta aula aprenderemos a usar dois deles, os dois primeiros.
O GDB atribui um nmero para cada ponto de parada quando da sua criao. Esses nmeros so nmeros inteiros sucessivos comeando com 1. Em diversos comandos para controle de pontos de parada, podemos usar seu nmero seqencial para referenci-lo. Assim que criado, um ponto de parada est habilitado. No entanto, durante a execuo de um programa, podemos desabilit-lo de acordo com nossa convenincia. Um breakpoint um ponto de parada que interrompe a execuo de um programa em um dado ponto especicado do cdigo fonte. Seu uso mais freqente com o argumento do ponto de execuo como sendo o nmero da linha do cdigo fonte. Por exemplo, breakpoint 5 estabelece um ponto de parada na linha 5 do programa. O ponto de parada interromper o programa antes da execuo do cdigo localizado na linha especicada. O comando breakpoint pode ser abreviado por br . Outra forma de uso de um breakpoint atravs de sua associao a uma expresso lgica, estabelecendo um ponto de parada em uma dada localizao do cdigo fonte, caso uma determinada condio seja satisfeita. Por exemplo, breakpoint 13 if ant == prox estabelece um ponto de parada na linha 13, caso a expresso lgica descrita aps a palavra reservada if seja avaliada com valor verdadeiro. O ponto de parada interromper ento o programa antes da execuo do cdigo na linha 22. Por outro lado, um breakpoint tambm pode ser criado atravs do comando breakpoint identificador , onde o argumento identificador o nome de uma funo. At o momento, projetamos programas apenas com a funo com identicador main para solucionar problemas propostos, mas em breve projetaremos outras funes e este comando pode nos ajudar a adicionar pontos de parada no incio de outras funes de um programa. Podemos tambm usar um ponto de parada na execuo de um programa que suspende sua execuo sempre que o valor de uma determinada expresso se modica, sem ter de predizer um ponto particular no cdigo fonte onde isso acontece. Esse ponto de parada chamado de watchpoint do GDB. Por exemplo, watch iguais estabelece um ponto de parada que ir suspender a execuo do programa quando o valor da expresso descrita que no caso contm apenas a varivel iguais for modicado. O comando watch pode ser abreviado por wa . O watchpoint s pode ser adicionado durante a execuo de um programa. Como j mencionado, o GDB atribui um nmero seqencial, iniciando com 1, para cada ponto de parada, quando da sua criao. Podemos imprimir uma tabela com informaes bsicas sobre os pontos de parada associados ao programa em execuo com o comando info breakpoints [n] , que pode ser abreviado por info break [n] . O argumento opcional n mostra as informaes apenas do ponto de parada especicado. Por exemplo,
(gdb) info break Num Type Disp Enb Address 1 breakpoint keep y 0x080483f5 breakpoint already hit 1 time 2 breakpoint keep y 0x0804848e stop only if ant == prox 3 hw watchpoint keep y (gdb)
DCT
UFMS
31.5 P ROGRAMA
FONTE
185
Muitas vezes, queremos eliminar um ponto de parada que j cumpriu o seu papel na depurao do programa e no queremos mais que o programa seja interrompido naquele ponto previamente especicado. Podemos usar os comandos clear ou delete para remover um ou mais pontos de parada de um programa. Os comandos clear e delete , com um argumento numrico, removem um ponto de parada especicado. Por exemplo, os comandos clear 1 e delete 2 removem os pontos de parada identicados pelos nmeros 1 e 2, respectivamente. A diferena entre esses comandos se d quando so usados sem argumentos: o comando clear remove o ponto de parada da linha de cdigo em execuo, se existir algum, e o comando delete sem argumentos remove todos os pontos de parada existentes no programa. Ao invs de remover denitivamente um ponto de parada, pode ser til para o processo de depurao apenas desabilit-lo temporariamente e, se for necessrio, reabilit-lo depois. O comando disable desabilita todos os pontos de parada de um programa. Podemos desabilitar um ponto de parada especco atravs do uso de seu nmero como argumento desse comando disable . Por exemplo, disable 3 desabilita o ponto de parada de nmero 3. Podemos tambm reabilitar um ponto de parada atravs do comando enable , que reabilita todos os pontos de parada desabilitados anteriormente, ou enable n , que reabilita o ponto de parada de nmero n . Aps vericar o que aconteceu com o programa em um dado ponto de parada, podemos retomar a execuo do programa a partir deste ponto de dois modos diferentes: atravs do comando continue , que pode ser abreviado por c , ou atravs do comando step , que pode ser abreviado por s . No primeiro comando, a execuo retomada e segue at o nal do programa ou at um outro ponto de parada ser encontrado. No segundo, apenas a prxima sentena do programa em geral, a prxima linha executada e o controle do programa novamente devolvido ao GDB. O comando step pode vir seguido por um argumento numrico, indicando quantas sentenas do cdigo fonte queremos progredir na sua execuo. Por exemplo, step 5 indica que queremos executar as prximas 5 sentenas do cdigo. Caso um ponto de parada seja encontrado antes disso, o programa interrompido e o controle repassado ao GDB. Comumente, adicionamos um ponto de parada no incio de uma funo onde acreditamos que exista um problema, um erro, executamos o programa at que ele atinja este ponto de parada e ento usamos o comando step nessa rea suspeita, examinando o contedo das variveis envolvidas, at que o problema se revele.
31.6 V ERIFICAO
DE DADOS
186
(gdb) list 1,14 1 #include <stdio.h> 2 int main(void) 3 { 4 int num, ant, prox, iguais; 5 printf("\nInforme um nmero: "); 6 scanf("%d", &num); 7 prox = num % 10; 8 iguais = 0; 9 while (num != 0 && !iguais) { 10 num = num / 10; 11 ant = prox; 12 prox = num % 10; 13 if (ant == prox) 14 iguais = 1; (gdb)
Uma facilidade que o GDB nos oferece mostrar o valor de uma expresso freqentemente. Dessa forma, podemos vericar como essa expresso se comporta durante a execuo do programa. Podemos criar uma lista de impresso automtica que o GDB mostra cada vez que o programa tem sua execuo interrompida. Podemos usar o comando display para adicionar uma expresso lista de impresso automtica de um programa. Por exemplo, display iguais adiciona a varivel iguais a essa lista e faz com que o GDB imprima o contedo dessa verivel sempre que a execuo do programa for interrompida por algum evento. Aos elementos inseridos nessa lista so atribudos nmeros inteiros consecutivos, iniciando com 1. Para remover uma expresso dessa lista, basta executar o comando delete display n, onde n o nmero da expresso que desejamos remover. Para visualizar todas as expresses nesta lista necessrio executar o comando info display . Todas as variveis internas de uma funo, e seus contedos, podem ser visualizados atravs do comando info locals .
31.8 R ESUMO
DOS COMANDOS
187
tedo de uma varivel. Por exemplo, set var x = 2 modica o contedo da varivel x para 2. O lado direito aps o smbolo = no comando set var pode conter uma constante, uma varivel ou uma expresso vlida. Essa caracterstica do GDB permite que um(a) programador(a) possa alterar os contedos de variveis durante a execuo de um programa e, com isso, possa controlar seu uxo de execuo e corrigir possveis erros.
DCT
UFMS
31.9 E XEMPLOS
DE EXECUO
188
#include <stdio.h> int main(void) { int num, ant, prox, iguais; printf("Informe um nmero: "); scanf("%d", &num); prox = num % 10; iguais = 0; while (num != 0 && !iguais) { num = num / 10; ant = prox; prox = num % 10; if (ant == prox) iguais = 1; } if (iguais) printf("Nmero tem dois dgitos consecutivos iguais.\n"); else printf("Nmero no tem dois dgitos consecutivos iguais.\n"); return 0; }
Iniciamos o processo compilando o programa 31.1 adequadamente e carregando o programa executvel gerado no GDB:
$ gcc consecutivos.c -o consecutivos -Wall -ansi -pedantic -g $ gdb ./consecutivos GNU gdb 6.8-debian Copyright (C) 2008 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i486-linux-gnu"... (gdb)
DCT
UFMS
31.9 E XEMPLOS
DE EXECUO
189
Para vericar se o programa foi de fato carregado na chamada do GDB, podemos usar o comando list para ver seu cdigo fonte ou ento o comando info program , para visualizar o estado do programa:
(gdb) list 1,12 1 #include <stdio.h> 2 int main(void) 3 { 4 int num, ant, prox, iguais; 5 printf("Informe um nmero: "); 6 scanf("%d", &num); 7 prox = num % 10; 8 iguais = 0; 9 while (num != 0 && !iguais) { 10 num = num / 10; 11 ant = prox; 12 prox = num % 10; (gdb)
ou
(gdb) info program The program being debugged is not being run. (gdb)
(gdb) run Starting program: ~/ensino/disciplinas/progi/2009/programas/consecutivos Informe um nmero: 12233 Nmero tem dois dgitos consecutivos iguais. Program exited normally. (gdb)
A execuo acima foi realizada dentro do GDB, sem a adio de qualquer ponto de parada. Como o programa ainda continua carregado no GDB, podemos execut-lo quantas vezes quisermos. Antes de iniciar a prxima execuo, vamos adicionar alguns pontos de parada no programa:
DCT
UFMS
31.9 E XEMPLOS
DE EXECUO
190
(gdb) run Starting program: ~/ensino/disciplinas/progi/2009/programas/consecutivos Breakpoint 1, main () at consecutivos.c:5 5 printf("Informe um nmero: "); (gdb)
O ponto de parada estabelecido na linha 5 do programa foi encontrado e o GDB interrompeu a execuo do programa neste ponto. Podemos visualizar todas as variveis, e seus contedos, dentro da funo main usando o comando info locals :
(gdb) info locals num = -1209114636 ant = 134518608 prox = -1075753928 iguais = 134513993 (gdb)
Observe que todas as variveis declaradas constam da relao apresentada pelo GDB e que seus contedos so valores estranhos, j que nenhuma dessas variveis foi inicializada at a linha 5 do programa. Vamos adicionar mais pontos de parada neste programa, um deles um breakpoint associado a uma expresso lgica e o outro um watchpoint associado a uma varivel:
(gdb) break 13 if ant == prox Breakpoint 2 at 0x80484ce: file consecutivos.c, line 13. (gdb) watch iguais Hardware watchpoint 3: iguais (gdb)
Agora, podemos visualizar todos os pontos de parada associados a este programa com o comando info break :
DCT UFMS
31.9 E XEMPLOS
DE EXECUO
191
(gdb) info break Num Type Disp Enb Address 1 breakpoint keep y 0x08048416 breakpoint already hit 1 time 2 breakpoint keep y 0x080484ce stop only if ant == prox 3 hw watchpoint keep y (gdb)
(gdb) continue Continuing. Informe um nmero: 55123 Hardware watchpoint 3: iguais Old value = 134513993 New value = 0 0x08048471 in main () at consecutivos.c:8 8 iguais = 0; (gdb)
Observe que na linha 8 do programa a varivel iguais inicializada, substituindo ento um valor no vlido, um lixo que iguais continha aps sua declarao, pelo valor 0 (zero). Assim, o GDB interrompe a execuo do programa nessa linha, devido ao watchpoint de nmero 3, e mostra a mudana de valores que ocorreu nessa varivel. Vamos ento continuar a execuo do programa:
O GDB ento interrompeu a execuo do programa devido ao breakpoint 2, na linha 13 do programa. Neste ponto, deve ter ocorrido o evento em que os contedos das variveis ant e prox coincidem. Para vericar se esse evento de fato ocorreu, podemos usar o comando print para vericar os contedos dessas variveis:
DCT UFMS
31.9 E XEMPLOS
DE EXECUO
192
(gdb) continue Continuing. Hardware watchpoint 3: iguais Old value = 0 New value = 1 main () at consecutivos.c:9 9 while (num != 0 && !iguais) { (gdb)
Observe que o programa foi interrompido pelo watchpoint, j que o contedo da varivel iguais foi modicado de 0 para 1 na linha 14 do programa. Assim, a prxima linha a ser executada a linha 9, como mostrado pelo GDB. Vamos dar uma olhada no contedo das variveis da funo main :
(gdb) continue Continuing. Nmero tem dois dgitos consecutivos iguais. Watchpoint 3 deleted because the program has left the block in which its expression is valid. 0xb7f08250 in ?? () from /lib/ld-linux.so.2 (gdb)
DCT
UFMS
31.9 E XEMPLOS
DE EXECUO
193
Mais uma anlise e mais alguns testes sobre o programa 31.1 nos permitem armar com alguma convico que este programa est correto e que soluciona o exerccio 11.3. O fato novo que essa armao foi feita tambm com o GDB nos auxiliando. Para provar formalmente que o programa 31.1 est correto, devemos descrever algum invariante do processo iterativo das linhas 9 at 15 do programa e ento provar por induo a sua validade, conforme a aula 20. No prximo exemplo vamos executar um programa sob o controle do GDB que no soluciona o problema associado, isto , contm algum erro que, primeira vista, no conseguimos corrigir. O programa 31.2 se prope a implementar a ordenao por seleo. Programa 31.2: Uma verso de um programa que deveria realizar a ordenao por seleo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
#include <stdio.h> int main(void) { int i, j, n, menor, indice, aux, A[10]; scanf("%d", &n); for (i = 0; i < n; i++) scanf("%d", &A[i]); for (i = 0; i < n-1; i++) { menor = A[i]; indice = i; for (j = n-1; j > i; j++) { if (A[j] < menor) { menor = A[j]; indice = j; } } aux = A[i]; A[i] = A[indice]; A[indice] = aux; } for (i = 0; i < n; i++) printf("%d ", A[i]); printf("\n"); return 0; }
DCT
UFMS
31.9 E XEMPLOS
DE EXECUO
194
Esse erro no nos d nenhuma dica de onde procur-lo. E se j nos debruamos sobre o cdigo fonte e no conseguimos encontr-lo, a melhor idia e usar um depurador para nos ajudar. Neste exemplo, mostraremos o uso do GDB sem muitas interrupes no texto, a menos que necessrias. Ento, segue uma depurao do programa 31.2.
$ gcc ord-selecao.c -o ord-selecao -Wall -ansi -pedantic -g $ gdb ./ord-selecao GNU gdb 6.8-debian Copyright (C) 2008 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i486-linux-gnu"... (gdb) list 8,15 8 for (i = 0; i < n-1; i++) { 9 menor = A[i]; 10 indice = i; 11 for (j = n-1; j > i; j++) { 12 if (A[j] < menor) { 13 menor = A[j]; 14 indice = j; 15 } (gdb) break 8 Breakpoint 1 at 0x804845c: file ord-selecao.c, line 8. (gdb) run Starting program: ~/ensino/disciplinas/progi/2009/programas/ord-selecao 8 7 5 4 1 8 6 2 3 Breakpoint 1, main () at ord-selecao.c:8 8 for (i = 0; i < n-1; i++) { (gdb) step 4 12 if (A[j] < menor) { (gdb) info locals i = 0 j = 7 n = 8 menor = 7 indice = 0 aux = -1208630704 A = {7, 5, 4, 1, 8, 6, 2, 3, 134518484, -1075407768} (gdb) display A[j] 1: A[j] = 3 (gdb) display j 2: j = 7 (gdb) display i 3: i = 0
DCT
UFMS
31.9 E XEMPLOS
DE EXECUO
195
(gdb) watch menor Hardware watchpoint 2: menor (gdb) continue Continuing. Hardware watchpoint 2: menor Old value = 7 New value = 3 main () at ord-selecao.c:14 14 indice = j; 3: i = 0 2: j = 7 1: A[j] = 3 (gdb) continue Continuing. Hardware watchpoint 2: menor Old value = 3 New value = -1075407768 main () at ord-selecao.c:14 14 indice = j; 3: i = 0 2: j = 9 1: A[j] = -1075407768 (gdb) c Continuing. Hardware watchpoint 2: menor Old value = -1075407768 New value = -1208630704 main () at ord-selecao.c:14 14 indice = j; 3: i = 0 2: j = 15 1: A[j] = -1208630704 (gdb) step 100 Hardware watchpoint 2: menor Old value = -1208630704 New value = -1210084267 main () at ord-selecao.c:14 14 indice = j; 3: i = 0 2: j = 18 1: A[j] = -1210084267 (gdb) set var j=-1 (gdb) set var i=n (gdb) continue Continuing.
DCT
UFMS
31.9 E XEMPLOS
DE EXECUO
196
7 5 4 1 8 6 2 3 Watchpoint 2 deleted because the program has left the block in which its expression is valid. 0xb7f5c251 in ?? () from /lib/ld-linux.so.2 (gdb)
Encontramos um erro na linha 11 do programa, no passo da estrutura de repetio for . Substituindo j++ por j-- nessa linha, temos um programa corrigido que realiza uma ordenao por seleo de n nmeros inteiros.
DCT
UFMS
A ULA 32
E FICINCIA
DE PROGRAMAS
Medir a ecincia de um programa signica tentar predizer os recursos necessrios para seu funcionamento. O recurso que temos mais interesse o tempo embora a memria, a comunicao e as portas lgicas tambm podem ser de interesse. Na anlise de programas alternativos para soluo de um mesmo problema, aqueles mais ecientes, isto , que usam a menor quantidade de tempo, so em geral escolhidos como melhores programas. Nesta aula faremos uma discusso inicial sobre ecincia de programas.
32.1 Programas
Um programa, como j sabemos, uma seqncia bem denida de passos descritos em uma linguagem de programao especca que transforma um conjunto de valores, chamado de entrada, e produz um conjunto de valores, chamado de sada. Dessa forma, um programa tambm uma ferramenta para solucionar um problema computacional bem denido. Suponha que temos um problema computacional bem denido, o problema da busca, que j vimos antes. A descrio do problema dada a seguir: Dado um nmero intiro n, com 1 n 100, um conjunto C de n nmeros inteiros e um nmero inteiro x, vericar se x se encontra no conjunto C . O problema da busca um problema computacional bsico que surge em diversas aplicaes prticas. A busca uma operao bsica em computao e, por isso, vrios bons programas que a realizam foram desenvolvidos. A escolha do melhor programa para uma dada aplicao depende de alguns fatores como a quantidade de elementos no conjunto C e da complexidade da estrutura em que C est armazenado. Como j vimos, se para qualquer entrada um programa pra com a resposta correta, ento dizemos que o mesmo est correto. Assim, um programa correto soluciona o problema computacional associado. Um programa incorreto pode sequer parar, para alguma entrada, ou pode parar mas com uma resposta indesejada. O programa 32.1 implementa uma busca de um nmero inteiro x em um conjunto de n nmeros inteiros C armazenado na memria como um vetor. A busca se d seqencialmente no vetor C , da esquerda para direita, at que um elemento com ndice i no vetor C contenha o valor x, quando o programa responde que o elemento foi encontrado, mostrando sua posio. Caso contrrio, o vetor C todo percorrido e o programa informa que o elemento x no se encontra no vetor C . 197
32.2 A NLISE
DE ALGORITMOS E PROGRAMAS
198
#include <stdio.h> #define MAX 100 int main(void) { int n, i, C[MAX], x; printf("Informe n: "); scanf("%d", &n); for (i = 0; i < n; i++) { printf("Informe um elemento: "); scanf("%d", &C[i]); } printf("Informe x: "); scanf("%d", &x); for (i = 0; i < n && C[i] != x; i++) ; if (i < n) printf("%d est na posio %d de C\n", x, i); else printf("%d no pertence ao conjunto C\n", x); return 0; }
32.2 A NLISE
DE ALGORITMOS E PROGRAMAS
199
No difcil notar que a anlise do programa 32.1 depende do nmero de elementos fornecidos na entrada. Ou seja, procurar um elemento x em um conjunto C com milhares de elementos gasta mais tempo que procur-lo em um conjunto C com trs elementos. Alm disso, o programa 32.1 gasta diferentes quantidades de tempo para buscar um elemento em conjuntos de mesmo tamanho, dependendo de como os elementos nesses conjuntos esto dispostos. Em geral, o tempo gasto por um programa cresce com o tamanho da entrada e assim comum descrever o tempo de execuo de um programa como uma funo do tamanho de sua entrada. Por tamanho da entrada queremos dizer, em geral, o nmero de itens na entrada. Por exemplo, o vetor de nmeros com n nmeros inteiros onde a busca de um elemento ser realizada. Em outros casos, como na multiplicao de dois nmeros inteiros, a melhor medida para o tamanho da entrada o nmero de bits necessrios para representar essa entrada na notao binria. O tempo de execuo de um programa sobre uma entrada particular o nmero de operaes primitivas ou passos executados por ele. Quanto mais independente da mquina a denio de um passo, mais conveniente para anlise de tempo dos programas. Considere ento que uma quantidade constante de tempo necessria para executar cada linha do programa. Uma linha pode gastar uma quantidade de tempo diferente de outra linha, mas consideramos que cada execuo da i-sima linha gasta tempo ci , onde ci uma constante. Iniciamos a anlise do programa 32.1 destacando que o aspecto mais importante para sua anlise o tempo gasto com a busca do elemento x no vetor C , descrita nas linhas 14 e 15. As linhas restantes contm diretivas de pr-processador, cabealho da funo main , a entrada de dados nas linhas 6 a 13, a sada nas linhas 16 a 19 e a sentena return que naliza a funo main na linha 20. Isto , queremos saber o tempo gasto no processamento, na transformao, dos dados de entrada nos dados de sada.
#include <stdio.h> #define MAX 100 int main(void) { int n, i, C[MAX], x; printf("Informe n: "); scanf("%d", &n); for (i = 0; i < n; i++) { printf("Informe um elemento: "); scanf("%d", &C[i]); } printf("Informe x: "); scanf("%d", &x); for (i = 0; i < n && C[i] != x; i++) ; if (i < n) printf("%d est na posio %d de C\n", x, i); else printf("%d no pertence ao conjunto C\n", x); return 0; }
DCT
Vezes 1 1 1 1 1 1 1 n+1 n n n 1 1 ti ti 1 1 1 1 1 1 1
UFMS
32.2 A NLISE
DE ALGORITMOS E PROGRAMAS
200
O tempo de execuo do programa 32.1 dado pela soma dos tempos para cada sentena executada. Uma sentena que gasta ci passos e executada n vezes contribui com ci n no tempo de execuo do programa. Para computar T (n), o tempo de execuo do programa 32.1, devemos somar os produtos das colunas Custo e Vezes , obtendo: T (n) = c1 + c2 + c3 + c4 + c5 + c6 + c7 (n + 1) + c8 n + c9 n+ c10 + c11 + c12 ti + c13 + c14 + c15 + c16 + c17 . Observe que, mesmo para entradas de um mesmo tamanho, o tempo de execuo de um programa pode depender de qual entrada desse tamanho fornecida. Por exemplo, no programa 32.1, o melhor caso ocorre se o elemento x encontra-se na primeira posio do vetor C . Assim, ti = 1 e o tempo de execuo do melhor caso dado por: T (n) = c1 + c2 + c3 + c4 + c5 + c6 + c7 (n + 1) + c8 n + c9 n+ c10 + c11 + c12 + c13 + c14 + c15 + c16 + c17 = (c7 + c8 + c9 )n+ (c1 + c2 + c3 + c4 + c5 + c6 + c7 + c10 + c11 + c12 + c13 + c14 + c15 + c16 + c17 ) . Esse tempo de execuo pode ser expresso como an + b para constantes a e b, que dependem dos custos ci das sentenas do programa. Assim, o tempo de execuo dado por uma funo linear em n. No entanto, observe que as constantes c7 , c8 e c9 que multiplicam n na frmula de T (n) so aquelas que tratam somente da entrada de dados. Evidentemente que para armazenar n nmeros inteiros em um vetor devemos gastar tempo an, para alguma constante a. Dessa forma, se consideramos apenas a constante c12 na frmula, que trata propriamente da busca, o tempo de execuo de melhor caso, quando ti = 1, dado por: T (n) = c12 ti = c12 . Por outro lado, se o elemento x no se encontra no conjunto C , temos ento o pior caso do programa. Alm de comparar i com n, comparamos tambm o elemento x com o elemento C [i] para cada i, 0 i n 1. Uma ltima comparao ainda realizada quando i atinge o valor n. Assim, ti = n + 1 e o tempo de execuo de pior caso dado por: T (n) = c12 ti = c12 (n + 1) = c12 n + c12 . Esse tempo de execuo de pior caso pode ser expresso como uma funo linear an + b para constantes a e b que dependem somente da constante c12 , responsvel pelo trecho do programa que realiza o processamento. Na anlise do programa 32.1, estabelecemos os tempos de execuo do melhor caso, quando encontramos o elemento procurado logo na primeira posio do vetor que representa o conjunto, e do pior caso, quando no encontramos o elemento no vetor. No entanto, estamos em geral interessados no tempo de execuo de pior caso de um programa, isto , o maior tempo de execuo para qualquer entrada de tamanho n. Como o tempo de execuo de pior caso de um programa um limitante superior para seu tempo de execuo para qualquer entrada, temos ento uma garantia que o programa nunca vai gastar mais tempo que esse estabelecido. Alm disso, o pior caso ocorre muito freqentemente nos programas em geral, como no caso do problema da busca.
DCT UFMS
32.2 A NLISE
DE ALGORITMOS E PROGRAMAS
201
32.3 A NLISE
202
cg(n)
f (n)
n n0 Figura 32.1: f (n) = O(g(n)). Dessa forma, podemos dizer, por exemplo, que 4n + 1 = O(n). Isso porque existem constantes positivas c e n0 tais que 4n + 1 cn para todo n n0 . Tomando c = 5 e n0 = 1, essa desigualdade satisfeita para todo n n0 . Certamente, existem outras escolhas para as constantes c e n0 , mas o mais importante que existe alguma escolha. Observe que as constantes dependem da funo 4n + 1. Uma funo diferente que pertence a O(n) provavelmente necessita de outras constantes. A denio de O(g(n)) requer que toda funo pertencente a O(g(n)) seja assintoticamente no-negativa, isto , que f (n) seja no-negativa sempre que n seja sucientemente grande. Conseqentemente, a prpria funo g(n) deve ser assintoticamente no-negativa, caso contrrio o conjunto O(g(n)) vazio. Portanto, vamos considerar que toda funo usada na notao O assintoticamente no-negativa. A ordem de crescimento do tempo de execuo de um programa ou algoritmo pode ser denotada atravs de outras notaes assintticas, tais como as notaes , , o e . Essas notaes so especcas para anlise do tempo de execuo de um programa atravs de outros pontos de vista. Nesta aula, caremos restritos apenas notao O.
DCT
UFMS
32.3 A NLISE
203
#include <stdio.h> #define MAX 100 int main(void) { int i, j, n, aux, A[MAX]; scanf("%d", &n); for (i = 0; i < n; i++) scanf("%d", &A[i]); for (i = n-1; i > 0; i--) for (j = 0; j < i; j++) if (A[j] > A[j+1]) { aux = A[j]; A[j] = A[j+1]; A[j+1] = aux; } for (i = 0; i < n; i++) printf("%d ", A[i]); printf("\n"); return 0; }
Conforme j zemos antes, a anlise linha a linha do programa 32.2 descrita abaixo.
#include <stdio.h> #define MAX 100 int main(void) { int i, j, n, aux, A[MAX]; scanf("%d", &n); for (i = 0; i < n; i++) scanf("%d", &A[i]); for (i = n-1; i > 0; i--) for (j = 0; j < i; j++) if (A[j] > A[j+1]) { aux = A[j]; A[j] = A[j+1]; A[j+1] = aux; } for (i = 0; i < n; i++) printf("%d ", A[i]); printf("\n"; return 0; }
DCT
32.3 A NLISE
204
Podemos ir um passo adiante na simplicao da anlise de um algoritmo ou programa se consideramos que cada linha tem custo de processamento 1. Na prtica, isso no bem verdade j que h sentenas mais custosas que outras. Por exemplo, o custo para executar uma sentena que contm a multiplicao de dois nmeros de ponto utuante de preciso dupla maior que o custo de comparar dois nmeros inteiros. No entanto, ainda assim vamos considerar que o custo para processar qualquer linha de um programa constante e, mais ainda, que tem custo 1. Dessa forma, a coluna com rtulo Vezes nas anlises acima representam ento o custo de execuo de cada linha do programa. Para anlise do programa 32.2 vamos considerar apenas o trecho onde ocorre o processamento, isto , as linhas de 9 a 15. O melhor caso para o programa 32.2 ocorre quando a seqncia de entrada com n nmeros inteiros fornecida em ordem no-decrescente. Observe que, nesse caso, as linhas 12, 13 e 14 no sero executadas sequer uma vez. Ou seja, ti = 0 para todo i. Ento, o tempo de execuo no melhor caso dado pela seguinte expresso:
n1 n1 n1 n1 n1
T (n) = n + =n+2
(i + 1) +
i=1 n1 i=1 n1
i+
i=1 n1
ti +
i=1
ti +
i=1
ti
i+
i=1 n1 i=1 n1
1+3
i=1 n1
ti 0
i=1
=n+2
i=1
i+
i=1
1+3
n(n 1) =n+2 +n1 2 = n2 + n 1 . Ento, escolhendo c = 2 e n0 = 1 temos que T (n) = O(n2 ). Ou seja, o tempo de execuo do melhor caso do programa 32.2 que implementa o mtodo da bolha quadrtico no tamanho da entrada. Para denir a entrada que determina o pior caso para o programa 32.2 devemos notar que quando o vetor contm os n elementos em ordem decrescente, o maior nmero de trocas realizado. Assim, ti = i para todo i e o tempo de execuo de pior caso dado pela seguinte expresso:
n1 n1 n1
(i + 1) +
i=1 n1 i=1 n1
i+3
i=1 n1
ti i
i=1
(i + 1) +
i=1 n1 i=1 n1
i+3 1
i+
i=1 i=1
=n+5 5 2 5 = 2 =
DCT
32.4 R ESUMO
205
Ento, escolhendo c = 5/2 e n0 = 1 temos que T (n) = O(n2 ). Ou seja, o tempo de execuo do pior caso do programa 32.2 quadrtico no tamanho da entrada. Note tambm que ambos os tempos de execuo de melhor e de pior caso do programa 32.2, que implementa o mtodo da ordenao da bolha, tm o mesmo desempenho assinttico.
32.4 Resumo
Um bom algoritmo ou programa como uma faca aada: faz o que suposto fazer com a menor quantidade de esforo aplicada. Usar um programa errado para resolver um problema como tentar cortar um bife com uma chave de fenda: podemos eventualmente obter um resultado digervel, mas gastaremos muito mais energia que o necessrio e o resultado no dever ser muito agradvel esteticamente. Programas alternativos projetados para resolver um mesmo problema em geral diferem dramaticamente em ecincia. Essas diferenas podem ser muito mais signicativas que a diferena de tempos de execuo em um supercomputador e em um computador pessoal. Como um exemplo, suponha que temos um supercomputador executando o programa que implementa o mtodo da bolha de ordenao, com tempo de execuo de pior caso O(n2 ), contra um pequeno computador pessoal executando o mtodo da intercalao. Esse ltimo mtodo de ordenao tem tempo de execuo de pior caso O(n log n) para qualquer entrada de tamanho n e ser visto na aula 43. Suponha que cada um desses programas deve ordenar um vetor de um milho de nmeros. Suponha tambm que o supercomputador capaz de executar 100 milhes de operaes por segundo enquanto que o computador pessoal capaz de executar apenas um milho de operaes por segundo. Para tornar a diferena ainda mais dramtica, suponha que o mais habilidoso dos programadores do mundo codicou o mtodo da bolha na linguagem de mquina do supercomputador e o programa usa 2n2 operaes para ordenar n nmeros inteiros. Por outro lado, o mtodo da intercalao foi programado por um programador mediano usando uma linguagem de programao de alto nvel com um compilador ineciente e o cdigo resultante gasta 50n log n operaes para ordenar os n nmeros. Assim, para ordenar um milho de nmeros o supercomputador gasta 2 (106 )2 operaes = 20.000 segundos 5,56 horas, 108 operaes/segundo enquanto que o computador pessoal gasta 50 106 log 106 operaes 1.000 segundos 16,67 minutos. 106 operaes/segundo Usando um programa cujo tempo de execuo tem menor taxa de crescimento, mesmo com um compilador pior, o computador pessoal 20 vezes mais rpido que o supercomputador! Esse exemplo mostra que os algoritmos e os programas, assim como os computadores, so uma tecnologia. O desempenho total do sistema depende da escolha de algoritmos e programas ecientes tanto quanto da escolha de computadores rpidos. Assim como rpidos avanos esto sendo feitos em outras tecnologias computacionais, eles esto sendo feitos em algoritmos e programas tambm.
DCT
UFMS
32.4 R ESUMO
206
Exerccios
32.1 Qual o menor valor de n tal que um programa com tempo de execuo 100n2 mais rpido que um programa cujo tempo de execuo 2n , supondo que os programas foram implementados no mesmo computador? 32.2 Suponha que estamos comparando as implementaes dos mtodos de ordenao da bolha e da intercalao em um mesmo computador. Para entradas de tamanho n, o mtodo da bolha gasta 8n2 passos enquanto que o mtodo da intercalao gasta 64n log n passos. Para quais valores de n o mtodo da bolha melhor que o mtodo da intercalao? 32.3 Expresse a funo n3 /1000 100n2 100n + 3 na notao O. 32.4 Para cada funo f (n) e tempo t na tabela abaixo determine o maior tamanho n de um problema que pode ser resolvido em tempo t, considerando que o programa soluciona o problema em f (n) microssegundos. 1 segundo log n n n n log n n2 n3 2n n! 32.5 verdade que 2n+1 = O(2n )? E verdade que 22n = O(2n )? 32.6 Considere o problema de computar o valor de um polinmio em um ponto. Dados n n1 i coecientes a0 , a1 , . . . , an1 e um nmero real x, queremos computar i =0 ai x . (a) Escreva um programa simples com tempo de execuo de pior caso O(n2 ) para solucionar este problema. (b) Escreva um programa com tempo de execuo de pior caso O(n) para solucionar este problema usando o mtodo chamado de regra de Horner para reescrever o polinmio:
n1 i=1
1 minuto
1 hora
1 dia
1 ms
1 ano
1 sculo
ai xi = ( (an1 x + an2 )x + + a1 )x + a0 .
32.7 Seja A[0..n 1] um vetor de n nmeros inteiros distintos dois a dois. Se i < j e A[i] > A[j ] ento o par (i, j ) chamado uma inverso de A. (a) Liste as cinco inverses do vetor A = 2, 3, 8, 6, 1 . (b) Qual vetor com elementos no conjunto {1, 2, . . . , n} tem a maior quantidade de inverses? Quantas so? (c) Escreva um programa que determine o nmero de inverses em qualquer permutao de n elementos em tempo de execuo de pior caso O(n log n).
DCT
UFMS
A ULA 33
I NTRODUO
S FUNES
Se concordamos que uma grande tarefa pode ser solucionada atravs de sua diviso em sub-tarefas e da combinao de suas solues parciais, ento podemos ter nosso trabalho facilitado focando na construo de solues para essas sub-tarefas. Em programao essas solues menores so chamadas de mdulos de programao e fazem parte de todo processo de construo de programas para soluo de problemas reais. Os mdulos so uma ferramenta da programao estruturada que fornecem ao() programador(a) um mecanismo para construir programas que so fceis de escrever, ler, compreender, corrigir, modicar e manter. Na linguagem C, essa caracterstica em um cdigo obtida atravs do uso de funes. Na verdade, todos os programas que codicamos at o momento de alguma forma j zeram uso de funes. Por exemplo, scanf e printf so funes de entrada e sada de dados da linguagem C que j usamos muitas e muitas vezes. Alm disso, temos construdo nossas prprias funes main para solucionar todos os problemas vistos at aqui. Apesar do termo funo vir da matemtica, uma funo da linguagem C nem sempre se parece com uma funo matemtica, j que pode no ter argumentos nem computar um valor. Cada funo na linguagem C pode ser vista como um pequeno programa com suas prprias declaraes de variveis e sentenas de programao. Alm de facilitar a compreenso e a modicao de um programa e evitar a duplicao de cdigo usado mais que uma vez, as funes podem ser usadas no s no programa para o qual foram projetadas, mas tambm em outros programas.
207
33.1 N OES
INICIAIS
208
A palavra reservada double na primeira linha o tipo do valor devolvido da funo media , que o tipo do valor devolvido pela funo cada vez que ela chamada. Os identicadores a e b , chamados de parmetros da funo, representam os dois nmeros que sero fornecidos quando a funo media chamada. Assim como uma varivel, um parmetro deve ter um tipo. No exemplo, ambos os parmetros a e b so do tipo double . Um parmetro de uma funo essencialmente uma varivel cujo valor inicial fornecido quando da sua chamada. Toda funo tem um trecho executvel, chamado de corpo, envolvido por abre e fecha chaves. O corpo de funo media consiste de uma nica sentena return . A execuo dessa sentena faz com que a funo regresse para o ponto de onde foi chamada. O valor (a + b) / 2 ser devolvido pela funo. Para chamar uma funo, escrevemos o nome da funo seguido por uma lista de argumentos. Por exemplo, media(x, y) uma chamada da funo media . Os argumentos so usados para fornecer informaes para a funo, como nesse caso, em que a funo media necessita de dois valores para calcular a mdia. O efeito da chamada media(x, y) primeiro copiar os valores de x e y nos parmetros a e b e, em seguida, executar o corpo de media . Um argumento no tem de ser necessariamente uma varivel. Qualquer expresso de um tipo compatvel pode ser um argumento, o que nos permite escrever media(5.1, 8.9) ou ainda media(x/2, y/3) . Devemos fazer uma chamada funo media no ponto do cdigo onde usamos o seu valor devolvido. Por exemplo, para computar a mdia de x e y , e escrever o resultado na sada, poderamos escrever:
A sentena acima tem o seguinte efeito: 1. a funo media chamada com argumentos x e y ; 2. x e y so copiados em a e b ; 3. a funo media executa sua sentena return , devolvendo a mdia de a e b ; 4. a funo printf imprime a cadeia de caracteres e o valor que media devolve, sendo que esse valor devolvido torna-se um argumento da funo printf . Observe que o valor devolvido pela funo media no salvo em lugar algum. A sentena imprime o valor e ento o descarta. Se h necessidade de usar o valor devolvido posteriormente, podemos captur-lo em uma varivel, como abaixo:
m = media(x, y);
DCT
UFMS
33.1 N OES
INICIAIS
209
A sentena acima chama a funo media e ento salva o valor devolvido na varivel m . Vamos usar a funo media em um programa. O programa 33.1 tem trs valores de entrada e computa a mdia desses valores dois a dois. Entre outras coisas, este programa mostra que uma funo pode ser chamada tantas vezes quanto necessrio. Programa 33.1: Primeiro exemplo do uso de funo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
#include <stdio.h> double media(double a, double b) { return (a + b) / 2; } int main(void) { double x, y, z; printf("Informe trs valores: "); scanf("%lf%lf%lf", &x, &y, &z); printf("Mdia de %g e %g %g\n", x, y, media(x, y)); printf("Mdia de %g e %g %g\n", x, z, media(x, z)); printf("Mdia de %g e %g %g\n", y, z, media(y, z)); return 0; }
Observe que a denio da funo media vem antes da denio da funo main , j que teramos problemas de compilao se zssemos o contrrio. Nem toda funo devolve um valor como a funo media faz. Por exemplo, uma funo cujo trabalho enviar informaes para a sada muito provavelmente no devolve valor algum. Para indicar que uma funo no tem valor devolvido, especicamos que seu valor devolvido do tipo void , onde void um tipo sem valor. Considere o seguinte exemplo, que imprime na sada a mensagem "n e contando..." , onde n um valor fornecido quando a funo chamada:
A funo imprimeContador tem um nico parmetro n do tipo int e no devolve valor algum. Nesse caso, especicamos void como o tipo do valor a ser devolvido e podemos opcionalmente omitir a sentena return . Dessa forma, a funo imprimeContador tambm poderia ser descrita corretamente como abaixo:
DCT
UFMS
33.1 N OES
INICIAIS
210
Como a funo imprimeContador no devolve um valor, no podemos cham-la da mesma maneira que chamamos a funo media no exemplo anterior. Ao contrrio, qualquer chamada da funo imprimeContador deve aparecer como uma nica sentena, como no exemplo a seguir:
imprimeContador(i);
O programa 33.2 chama a funo imprimeContador 10 vezes em uma estrutura de repetio for . Programa 33.2: Segundo exemplo do uso de funo.
1 2 3 4 5 6 7 8 9 10 11 12 13
#include <stdio.h> void imprimeContador(int n) { printf("%d e contando...\n", n); return; } int main(void) { int i; for (i = 10; i > 0; i--) imprimeContador(i); return 0; }
Inicialmente, a varivel i tem o valor 10. Quando a funo imprimeContador chamada pela primeira vez, o valor em i copiado no parmetro n e a partir de ento n tambm contm o valor 10. Como resultado, a primeira chamada da funo imprimeContador imprimir a seguinte mensagem:
10 e contando...
DCT
UFMS
33.1 N OES
INICIAIS
211
A funo imprimeContador regressa ento para o ponto em que foi chamada, o que acontece no corpo de uma estrutura de repetio for . A estrutura for continua sua execuo decrementando i para 9 e vericando se seu valor maior que 0. Como esse novo valor maior que 0, imprimeContador chamada novamente e imprime a mensagem abaixo:
9 e contando...
Cada vez que imprimeContador chamada, o valor armazenado na varivel i diferente e, assim, imprimeContador imprimir 10 mensagens diferentes na sada. importante salientar ainda que algumas funes podem tambm no ter parmetros. Considere a funo imprimeMsg , que imprime uma mensagem cada vez que chamada:
A palavra reservada void entre parnteses aps o identicador da funo indica que imprimeMsg no tem argumentos. Para chamar uma funo sem argumentos, devemos escrever o nome da funo seguido obrigatoriamente por parnteses, como mostramos abaixo:
imprimeMsg();
O programa 33.3 mostra o uso da funo imprimeMsg . Programa 33.3: Terceiro exemplo do uso de funo.
1 2 3 4 5 6 7 8 9 10 11
#include <stdio.h> void imprimeMsg(void) { printf("Programar bacana!\n"); return; } int main(void) { imprimeMsg(); return 0; }
DCT
UFMS
33.2 D EFINIO
E CHAMADA DE FUNES
212
A execuo do programa 33.3 comea com a primeira sentena na funo main , que a chamada funo imprimeMsg . Quando imprimeMsg comea sua execuo, ela chama a funo printf para mostrar uma mensagem na sada. Quando printf termina sua execuo, imprimeMsg termina sua execuo e volta para main .
A primeira linha apresentada a interface da funo e as linhas seguintes, envolvidas por chaves, compem o corpo da funo. A interface da funo inicia com um tipo , que especica
DCT UFMS
33.2 D EFINIO
E CHAMADA DE FUNES
213
o tipo do valor a ser devolvido pela funo. Funes no podem devolver variveis compostas homogneas, mas no h qualquer outra restrio quanto ao tipo de valor a ser devolvido. No entanto, especicar que o valor devolvido do tipo void indica que a funo no devolve um valor. Depois, a interface contm o identificador da funo, que seu nome e que a identica e diferencia de outras funes. E, nalmente, a interface contm os parmetros , que armazenam valores a serem recebidos como entrada pela funo, envolvidos por parnteses. A lista de parmetros composta por tipos e identicadores de variveis, separados por vrgulas. Um tipo deve ser especicado para cada parmetro, mesmo que vrios parmetros sejam do mesmo tipo. Por exemplo, errado escrever a funo media como abaixo:
Se a funo no tem parmetros, a palavra reservada void deve ocorrer entre os parnteses. Aps a interface, a funo contm um corpo, que uma seqncia de comandos da linguagem C envolvida por chaves. O corpo de uma funo pode incluir declaraes e sentenas. Por exemplo, a funo media poderia ser escrita como abaixo:
A declarao de variveis de uma funo deve vir primeiro, antes de qualquer sentena do corpo da funo. As variveis declaradas no corpo de uma funo pertencem exclusivamente quela funo e no podem ser examinadas ou modicadas por outras funes. Uma chamada de funo consiste de um identicador da funo seguido por uma lista de argumentos entre parnteses. Por exemplo, abaixo temos trs chamadas de funes:
Uma chamada de uma funo com valor devolvido void sempre seguida por um ; para torn-la uma sentena. Por exemplo:
DCT UFMS
33.2 D EFINIO
E CHAMADA DE FUNES
214
imprimeContador(i); imprimeMsg();
Por outro lado, uma chamada de uma funo com valor devolvido diferente de void produz um valor que pode ser armazenado em uma varivel ou usado em uma expresso aritmtica, relacional ou lgica. Por exemplo,
O valor devolvido por uma funo que devolve um valor diferente de void sempre pode ser descartado, como podemos ver na chamada a seguir:
media(x, y);
Esta chamada funo media um exemplo de uma sentena que avalia uma expresso mas descarta o resultado. Apesar de estranho na chamada funo media , ignorar o valor devolvido pode fazer sentido em alguns casos. Por exemplo, a funo printf sempre devolve o nmero de caracteres impressos na sada. Dessa forma, aps a chamada abaixo, a varivel nc ter o valor 19:
nc = printf("Programar bacana\n");
Como em geral no estamos interessados no nmero de caracteres impressos, normalmente descartamos o valor devolvido pela funo printf e fazemos:
printf("Programar bacana\n");
Uma funo que devolve um valor diferente de void deve usar a sentena return para especicar qual valor ser devolvido. A sentena return tem a seguinte forma geral:
return expresso;
DCT
UFMS
33.3 F INALIZAO
DE PROGRAMAS
215
Quando uma das sentenas acima executada, primeiramente a avaliao da expresso realizada e o valor obtido ento devolvido pela funo. Se o tipo da expresso na sentena return diferente do tipo devolvido pela funo, a expresso ser implicitamente convertida para o tipo da funo.
exit(0);
Como o argumento 0 no muito signicativo, a linguagem C nos permite usar o argumento EXIT_SUCCESS , que tem o mesmo efeito:
DCT UFMS
33.4 E XEMPLO
216
exit(EXIT_SUCCESS);
exit(EXIT_FAILURE);
EXIT_SUCCESS e EXIT_FAILURE so macros denidas na stdlib.h . Os valores associados so denidos pela arquitetura do computador em uso, mas em geral so 0 e 1 , respecti-
vamente. Como mtodos para terminar um programa, return e exit so muito semelhantes. Ou seja, a sentena
return expresso;
exit(expresso);
A diferena entre return e exit que exit causa o trmino de um programa independente do ponto em que se encontra essa chamada, isto , no considerando qual funo chamou a funo exit . A sentena return causa o trmino de um programa apenas se aparece na funo main . Alguns(mas) programadores(as) usam exit exclusivamente para depurar programas.
33.4 Exemplo
Vamos resolver agora o seguinte problema: Construa uma funo que receba dois inteiros positivos e devolva o mximo divisor comum entre eles. J resolvemos o problema de encontrar o mximo divisor comum de dois nmeros inteiros positivos antes, usando o algoritmo de Euclides, mas sem o uso de uma funo. Uma possvel funo que soluciona o problema acima mostrada a seguir.
DCT
UFMS
33.5 D ECLARAO
DE FUNES
217
Um exemplo de um programa principal que faz uma chamada funo mdc descrita acima mostrado no programa 33.4. Programa 33.4: Exemplo de um programa com uma funo mdc .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
#include <stdio.h> int mdc(int a, int b) { int aux; while (b != 0) { aux = a % b; a = b; b = aux; } return a; } int main(void) { int x, y; printf("Informe dois valores: "); scanf("%d%d", &x, &y); printf("O mdc entre %d e %d %d.", x, y, mdc(x, y)); return 0; }
33.5 D ECLARAO
DE FUNES
218
Suponha que rearranjamos o programa 33.1 colocando a denio da funo media depois da denio da funo main , como abaixo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
#include <stdio.h> int main(void) { double x, y, z; printf("Informe trs valores: "); scanf("%lf%lf%lf", &x, &y, &z); printf("Mdia de %g e %g %g\n", x, y, media(x, y)); printf("Mdia de %g e %g %g\n", x, z, media(x, z)); printf("Mdia de %g e %g %g\n", y, z, media(y, z)); return 0; } double media(double a, double b) { return (a + b) / 2; }
Quando o compilador encontra a primeira chamada da funo media na linha 7 da funo main , ele no tem informao alguma sobre media : no conhece quantos parmetros media tem, quais os tipos desses parmetros e qual o tipo do valor que media devolve. Ao invs de uma mensagem de erro, o compilador faz uma tentativa de declarao, chamada de declarao implcita, da funo media e, em geral, nos passos seguintes da compilao, um ou mais erros decorrem dessa declarao: um erro do tipo do valor devolvido, do nmero de parmetros ou do tipo de cada parmetro. Uma forma de evitar erros de chamadas antes da denio de uma funo dispor o programa de maneira que a denio da funo preceda todas as suas chamadas. Infelizmente, nem sempre possvel arranjar um programa dessa maneira e, mesmo quando possvel, pode tornar mais difcil sua compreenso j que as denies das funes sero dispostas em uma ordem pouco natural. Felizmente, a linguagem C oferece uma soluo melhor, com a declarao de uma funo antes de sua chamada. A declarao de uma funo fornece ao compilador uma viso inicial da funo cuja declarao completa ser dada posteriormente. A declarao de uma funo composta exatamente pela primeira linha da denio de uma funo com um ponto e vrgula adicionado no nal:
tipo identificador(parmetros);
Desnecessrio dizer que a declarao de uma funo tem de ser consistente com a denio da mesma funo. A seguir mostramos como nosso programa caria com a adio da declarao de media :
DCT
UFMS
33.5 D ECLARAO
DE FUNES
219
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/* declarao */
int main(void) { double x, y, z; printf("Informe trs valores: "); scanf("%lf%lf%lf", &x, &y, &z); printf("Mdia de %g e %g %g\n", x, y, media(x, y)); printf("Mdia de %g e %g %g\n", x, z, media(x, z)); printf("Mdia de %g e %g %g\n", y, z, media(y, z)); return 0; } double media(double a, double b) /* definio */ { return (a + b) / 2; }
A declarao de uma funo tambm conhecida como prottipo da funo. Um prottipo de uma funo fornece ao compilador uma descrio completa de como chamar a funo: quantos argumentos fornecer, de quais tipos esses argumentos devem ser e qual o tipo do resultado a ser devolvido.
Exerccios
33.1 (a) Escreva uma funo com a seguinte interface:
double areaTriangulo(double base, double altura)
que receba dois nmeros de ponto utuante que representam a base e a altura de um tringulo e compute e devolva a rea desse tringulo. (b) Escreva um programa que receba uma seqncia de n pares de nmeros de ponto utuante, onde cada par representa a base e a altura de um tringulo, e calcule e escreva, para cada par, a rea do tringulo correspondente. Use a funo descrita no item (a).
DCT
UFMS
33.5 D ECLARAO
DE FUNES
220
#include <stdio.h> double areaTriangulo(double base, double altura) { return (base * altura) / 2; } int main(void) { int i, n; double b, a; printf("Informe n: "); scanf("%d", &n); for (i = 0; i < n; i++) { printf("Informe a base e a altura do tringulo: "); scanf("%lf%lf", &b, &a); printf("rea do tringulo: %g\n", areaTriangulo(b, a)); } return 0; }
33.2
que receba dois nmeros inteiros positivos a e b e determine e devolva um valor que representa o produto desses nmeros, usando o seguinte mtodo de multiplicao: i. dividir, sucessivamente, o primeiro nmero por 2, at que se obtenha 1 como quociente; ii. em paralelo, dobrar, sucessivamente, o segundo nmero; iii. somar os nmeros da segunda coluna que tenham um nmero mpar na primeira coluna; o total obtido o produto procurado. Por exemplo, para os nmeros 9 e 6, temos que 9 6 9 6 6 4 12 2 24 1 48 48 54 (b) Escreva um programa que leia n 1 pares de nmeros e calcule os respectivos produtos desses pares, usando a funo do item (a). 33.3 Para determinar o nmero de lmpadas necessrias para cada aposento de uma residncia, existem normas que fornecem o mnimo de potncia de iluminao exigida por metro quadrado (m2 ) conforme o uso desse ambiente. Suponha que s temos lmpadas de 60 watts para uso.
DCT UFMS
33.5 D ECLARAO
DE FUNES
221
Seja a seguinte tabela de informaes sobre possveis aposentos de uma residncia: Utilizao quarto sala de TV salas cozinha varandas escritrio banheiro Classe 1 1 2 2 2 3 3 Potncia/m2 (W) 15 15 18 18 18 20 20
que receba um nmero inteiro representando a classe de iluminao de um aposento e dois nmeros com ponto utuante representando suas duas dimenses e devolva um nmero inteiro representando o nmero de lmpadas necessrias para iluminar adequadamente o aposento. (b) Escreva um programa que receba uma seqncia de informaes contendo o nome do aposento da residncia, sua classe de iluminao e as suas dimenses e, usando a funo numLampadas , imprima a rea de cada aposento, sua potncia de iluminao e o nmero total de lmpadas necessrias para o aposento. Alm disso, seu programa deve calcular o total de lmpadas necessrias e a potncia total necessria para a residncia toda. Suponha que o trmino se d quando o nome do aposento informado fim .
DCT
UFMS
A ULA 34
E XERCCIOS
34.1
que receba dois nmeros inteiros positivos a e b e calcule e devolva o mximo divisor comum entre eles. (b) Usando a funo do item anterior, escreva um programa que receba n inteiros positivos e calcule o mximo divisor comum entre todos eles. Programa 34.1: Soluo do exerccio 34.1.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
1 nmeros
#include <stdio.h> int mdc(int a, int b) { int aux; while (b != 0) { aux = a % b; a = b; b = aux; } return a; } int main(void) { int n, i, num, result; scanf("%d", &n); scanf("%d", &result); for (i = 2; i <= n; i++) { scanf("%d", &num); result = mdc(result, num); } printf("%d\n", result); return 0; }
222
que receba um nmero inteiro positivo p e verique se p primo, devolvendo 1 em caso positivo e 0 em caso negativo. (b) Usando a funo do item anterior, escreva um programa que receba n inteiros positivos e calcule a soma dos que so primos. 1 nmeros
34.3 Um nmero inteiro a dito ser permutao de um nmero inteiro b se os dgitos de a formam uma permutao dos dgitos de b. Exemplo: 5412434 uma permutao de 4321445, mas no uma permutao de 4312455. Observao: considere que o dgito 0 (zero) no ocorre nos nmeros. (a) Escreva uma funo com interface
int contaDigitos(int n, int d)
que receba dois nmeros inteiros n e d, com 0 < d 9, devolva um valor que representa o nmero de vezes que o dgito d ocorre no nmero n. (b) Usando a funo do item anterior, escreva um programa que leia dois nmeros inteiros positivos a e b e responda se a permutao de b. 34.4 (a) Escreva uma funo com interface
int sufixo(int a, int b)
que receba dois nmeros inteiros a e b e verique se b um suxo de a. Em caso positivo, a funo deve devolver 1; caso contrrio, a funo deve devolver 0. Exemplo: a b 567890 890 suxo 1234 1234 suxo 2457 245 no suxo 457 2457 no suxo (b) Usando a funo do item anterior, escreva um programa que leia dois nmeros inteiros a e b e verique se o menor deles subseqncia do outro. Exemplo: a 567890 1234 235
DCT
224 34.5 Uma seqncia de n nmeros inteiros no nulos dita m-alternante se constituda por m segmentos: o primeiro com um elemento, o segundo com dois elementos e assim por diante at o m-simo, com m elementos. Alm disso, os elementos de um mesmo segmento devem ser todos pares ou todos mpares e para cada segmento, se seus elementos forem todos pares (mpares), os elementos do segmento seguinte devem ser todos mpares (pares). Por exemplo: A seqncia com n = 3 elementos: 7 2 8 2-alternante. A seqncia com n = 10 elementos: 8 3 7 2 10 4 5 13 9 11 4-alternante.
A seqncia com n = 8 elementos: 1 12 4 3 13 5 8 6 no alternante, pois o ltimo segmento no tem tamanho 4. (a) Escreva uma funo com interface
int bloco(int m)
(b) Usando a funo do item anterior, escreva um programa que, dados um inteiro n, com n 1, e uma seqncia de n nmeros inteiros, verica se a seqncia malternante. O programa deve imprimir o valor de m ou exibir uma mensagem informando que a seqncia no alternante.
que receba como parmetro um inteiro m e leia m nmeros inteiros, devolvendo um dos seguintes valores: 0, se os m nmeros lidos forem pares; 1, se os m nmeros lidos forem mpares; 1, se entre os m nmeros lidos h nmeros com paridades diferentes.
DCT
UFMS
A ULA 35
A RGUMENTOS
E PARMETROS DE FUNES
Como vimos at aqui, a interface de uma funo dene muito do que queremos saber sobre ela: o tipo de valor que a funo devolve, seu identicador e seus parmetros. Isso implica na conseqente elevao do nvel de abstrao dos nossos programas, j que podemos entender o que um programa faz sem a necessidade de examinar os detalhes de suas funes. Caso alguns detalhes sejam de nosso interesse, tambm h a vantagem de que sabemos onde examin-los. Nesta aula focaremos nos argumentos e parmetros das funes e no escopo de seus dados.
x = quadrado(num);
o valor da expresso num um argumento da funo quadrado e atribudo ao parmetro correspondente da funo. A passagem desse valor feita por cpia, ou seja, o valor copiado no parmetro correspondente da funo quadrado . 225
35.1 A RGUMENTOS
E PARMETROS
226
J os valores dos parmetros de entrada e sada so passados/devolvidos por um mecanismo chamado referncia. Nesse caso, temos uma varivel especicada na chamada da funo e um parmetro especicado na interface da funo que compartilham a mesma rea de armazenamento na memria e isso signica que qualquer alterao realizada no contedo do parmetro dentro da funo acarreta alterao no contedo da varivel que o argumento da chamada. Vejamos um exemplo no programa 35.1. Programa 35.1: Exemplo de passagem de parmetros por referncia.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
#include <stdio.h> void decompoe(float x, int *parte_int, float *parte_frac) { *parte_int = (int) x; *parte_frac = x - *parte_int; return; } int main(void) { float num, b; int a; printf("Informe um nmero de ponto flutuante: "); scanf("%f", &num); decompoe(num, &a, &b); printf("Nmero: %f\n", num); printf("Parte inteira: %d\n", a); printf("Parte fracionria: %f\n", b); return 0; }
Note que os parmetros parte_int e parte_frac vem precedidos do smbolo * . Essa a forma que denimos parmetros de entrada e sada na linguagem C. Observe agora o corpo da funo decompoe :
DCT
UFMS
35.2 E SCOPO
DE DADOS E DE FUNES
227
Os parmetros de entrada e sada so usados como de costume no corpo de uma funo, isto , como vimos usando os parmetros de entrada. No entanto, os parmetros de entrada e sada sempre devem vir precedidos pelo smbolo * no corpo de uma funo, como mostrado acima. E por ltimo, a chamada da funo decompoe na funo main ligeiramente diferente, como destacado abaixo:
Observe que os argumentos a e b so precedidos pelo smbolo & , indicando que as variveis a e b da funo main , de tipos inteiro e ponto utuante respectivamente, so passadas como parmetros de entrada e sada para a funo decompoe , ou seja, so passadas por referncia, e qualquer alterao realizada nos parmetros correspondentes da funo reetiro nos argumentos da chamada.
DCT
UFMS
35.2 E SCOPO
DE DADOS E DE FUNES
228
Exerccios
35.1 (a) Escreva uma funo com a seguinte interface:
void troca(int *a, int *b)
que receba dois nmeros inteiros a e b e troque os seus contedos. (b) Usando a funo troca denida acima, escreva um programa que leia um vetor contendo n nmeros inteiros, com 1 n 100, ordene seus elementos em ordem no decrescente usando o mtodo das trocas sucessivas e imprima o vetor resultante na sada. (c) Repita a letra (b) implementando o mtodo da seleo. (d) Repita a letra (b) implementando o mtodo da insero. Programa 35.2: Soluo do exerccio 35.1(b).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
#include <stdio.h> #define MAX 100 void troca(int *a, int *b) { int aux; aux = *a; *a = *b; *b = aux; return; } int main(void) { int i, j, n, A[MAX]; scanf("%d", &n); for (i = 0; i < n; i++) scanf("%d", &A[i]); for (i = n-1; i > 0; i--) for (j = 0; j < i; j++) if (A[j] > A[j+1]) troca(&A[j], &A[j+1]); for (i = 0; i < n; i++) printf("%d ", A[i]); printf("\n"); return 0; }
DCT
UFMS
35.2 E SCOPO
DE DADOS E DE FUNES
229
35.2 Em um dado pas a moeda corrente possui apenas quatro cdulas de papel: $1, $5, $10 e $20. (a) Escreva uma funo com a seguinte interface:
void cedulas(int valor, int *um, int *cinco, int *dez, int *vinte)
que receba um valor em dinheiro e determine a menor quantidade de cdulas de 1, 5, 10 e 20 necessrias para pagar o valor especicado. (b) Escreva um programa que dado um valor na moeda corrente determine a menor quantidade de cdulas para pagar esse valor. Use a funo do item (a). 35.3 Dizemos que um nmero natural n palndromo se lido da esquerda para direita e da direita para esquerda o mesmo nmero. Exemplos: 567765 palndromo. 32423 palndromo. 567675 no palndromo. (a) Escreva uma funo com a seguinte interface:
void quebra(int n, int *prim, int *ult, int *miolo)
que receba um nmero do tipo inteiro n > 0 e devolva trs valores do tipo inteiro: o primeiro dgito de n, o ltimo dgito de n e um inteiro que represente o nmero n sem seu primeiro e ltimo dgitos. Exemplo: valor inicial de n 732 14738 78 7 primeiro dgito 7 1 7 7 ltimo dgito 2 8 8 7 miolo de n 3 473 0 0
(b) Usando a funo do item (a), escreva um programa que receba um inteiro positivo n > 0 e verique se n palndromo. Suponha que n no contm o dgito 0. 35.4 (a) Escreva uma funo com a seguinte interface:
int divisao(int *m, int *n, int d)
que receba trs valores positivos do tipo inteiro m, n e d e devolva 1 se d divide m, n ou ambos, e 0, caso contrrio. Alm disso, em caso positivo, a funo deve devolver um valor que representa o quociente da diviso de m por d e outro valor que representa o quociente da diviso de n por d.
DCT UFMS
35.2 E SCOPO
DE DADOS E DE FUNES
230
(b) Escreva um programa que leia dois inteiros positivos m e n e calcule, usando a funo do item (a), o mnimo mltiplo comum entre m e n. 35.5 (a) Escreva uma funo com a seguinte interface:
int somabit(int b1, int b2, int *vaium)
que receba trs valores inteiros e devolva um valor do tipo inteiro representando a soma dos trs bits, e devolva tambm um outro valor do tipo inteiro representando o valor do vai um. (b) Escreva um programa que leia dois nmeros no sistema binrio de numerao e, usando a funo do item (a), calcule um nmero em binrio que a soma dos dois nmeros dados.
DCT
UFMS
A ULA 36
F UNES
E VETORES
Nas aulas 33, 34 e 35 vimos funes, argumentos de funes e passagem de parmetros para funes na linguagem C, onde trabalhamos apenas com argumentos de tipos bsicos. Observe que chegamos a passar o valor de um elemento de um vetor como um argumento para uma funo, por cpia e por referncia. Nesta aula veremos como realizar passagem de parmetros de tipos complexos, mais especicamente de parmetros que so variveis compostas homogneas unidimensionais ou vetores.
#include <stdio.h> int minimo(int A[10]) { int i, min; min = A[0]; for (i = 1; i < 10; i++) if (A[i] < min) min = A[i]; return min; } int main(void) { int i, vet[10]; for (i = 0; i < 10; i++) scanf("%d", &vet[i]); printf("O menor valor do conjunto %d\n", minimo(vet)); return 0; }
231
36.2 V ETORES
232
Observe a interface da funo minimo do programa acima. Essa interface indica que a funo devolve um valor do tipo inteiro, tem identicador minimo e tem como seu argumento um vetor contendo 10 elementos do tipo inteiro. Referncias feitas ao parmetro A , no interior da funo minimo , acessam os elementos apropriados dentro do vetor que foi passado funo. Para passar um vetor inteiro para uma funo necessrio apenas descrever o identicador do vetor, sem qualquer referncia a ndices, na chamada da funo. Essa situao pode ser visualizada na ltima linha do programa principal, na chamada da funo minimo(pontos) . importante destacar tambm que a nica restrio de devoluo de uma funo relativa s variveis compostas homogneas. De fato, um valor armazenado em uma varivel de qualquer tipo pode ser devolvido por uma funo, excluindo variveis compostas homogneas.
#include <stdio.h> void dobro(int vetor[100], int n) { int i; for (i = 0; i < n; i++) vetor[i] = vetor[i] * 2; return; } int main(void) { int i, n, valor[100]; printf("Informe a quantidade de elementos: "); scanf("%d", &n); for (i = 0; i < n; i++) { printf("Informe o %d-simo valor: ", i+1); scanf("%d", &valor[i]); } dobro(valor, n); printf("Resultado: "); for (i = 0; i < n; i++) printf("%d ", valor[i]); printf("\n"); return 0; }
DCT
UFMS
36.3 V ETORES
233
Perceba que a funo dobro do programa acima modica os valores do vetor valor , uma varivel local do programa principal que passada como parmetro para a funo dobro na varivel vetor , uma varivel local da funo dobro . importante observar que, quando usamos vetores como argumentos, uma funo que modica o valor de um elemento do vetor, modica tambm o vetor original que foi passado como parmetro para a funo. Esta modicao tem efeito mesmo aps o trmino da execuo da funo. Nesse sentido, podemos dizer que um vetor, uma matriz ou uma varivel composta homognea de qualquer dimenso sempre um parmetro de entrada e sada, isto , sempre passado por referncia a uma funo. No entanto, lembre-se que a modicao de elementos de um vetor em uma funo se aplica somente quando o vetor completo passado como parmetro funo e no elementos individuais do vetor. Esse ltimo caso j foi tratado em aulas anteriores.
void dobro(int vetor[], int n) { int i; for (i = 0; i < n; i++) vetor[i] = vetor[i] * 2; return; }
Observe que a dimenso do parmetro vetor da funo dobro , que um vetor, foi omitida. A duas denies da funo dobro , nesta seo e na seo anterior, so equivalentes. No entanto, a denio com a dimenso omitida permite que a funo dobro possa ser chamada com vetores de quaisquer dimenses como argumentos. Por exemplo, se temos dois vetores de inteiros A e B , com dimenses 50 e 200, respectivamente, a funo dobro pode ser chamada para computar o dobro de cada coordenada do vetor A e, do mesmo modo, tambm pode se chamada para computar o dobro de cada coordenada do vetor B . Ou seja, a funo dobro pode ser chamada com vetores de dimenses diferentes como parmetros.
DCT
UFMS
36.3 V ETORES
234
Exerccios
36.1 (a) Escreva uma funo com a seguinte interface:
int subconjunto(int A[MAX], int m, int B[MAX], int n)
(b) Escreva um programa que receba dois vetores de nmeros inteiros U e W , com u e w elementos respectivamente, 1 u, w 100, e verique se os dois conjuntos so iguais (U = W se e somente se U W e W U ). Use a funo do item (a). Programa 36.3: Soluo do exerccio 36.1.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
que receba como parmetros um vetor A de nmeros inteiros com m elementos e um vetor B de nmeros inteiros com n elementos, ambos representando conjuntos, e verica se A est contido em B (A B ).
#include <stdio.h> #define MAX 100 int subconjunto(int A[MAX], int m, int B[MAX], int n) { int i, j, contido; contido = 1; for (i = 0; i < m && contido; i++) { for (j = 0; j < n && B[j] != A[i]; j++) ; if (j == n) contido = 0; } return contido; } int main(void) { int tamU, i, U[MAX], tamW, j, W[MAX]; scanf("%d", &tamU); for (i = 0; i < tamU; i++) scanf("%d", &U[i]); scanf("%d", &tamW); for (j = 0; j < tamW; j++) scanf("%d", &W[j]); if (subconjunto(U, tamU, W, tamW) && subconjunto(W, tamW, U, tamU)) printf("U == W\n"); else printf("U != W\n"); return 0; }
DCT
UFMS
36.3 V ETORES
235
36.2 Em um programa na linguagem C, um conjunto pode ser representado por um vetor da seguinte forma: V[0] contm o nmero de elementos do conjunto; V[1], V[2], ... so os elementos do conjunto, sem repeties. (a) Escreva uma funo com a seguinte interface:
void intersec(int A[MAX+1], int B[MAX+1], int C[MAX+1])
que dados dois conjuntos de nmeros inteiros A e B , construa um terceiro conjunto C tal que C = A B . Lembre-se de que em C[0] a sua funo deve colocar o tamanho da interseco. (b) Escreva um programa que leia um nmero inteiro n 2 e uma seqncia de n conjuntos de nmeros inteiros, cada um com no mximo 100 elementos, e construa e imprima um vetor que representa a interseco dos n conjuntos. Observao: no leia todos os conjuntos de uma s vez. Leia os dois primeiros conjuntos e calcule a primeira interseco. Depois, leia o prximo conjunto e calcule uma nova interseco entre esse conjunto lido e o conjunto da interseco anterior, e assim por diante. 36.3 (a) Escreva uma funo com a seguinte interface:
void ordena_insercao(int A[MAX], int m)
que receba um vetor A de m nmeros inteiros distintos, com 1 m 100, e ordene os elementos desse vetor em ordem crescente usando o mtodo da insero. (b) Escreva uma funo com a seguinte interface:
void intercala(int A[MAX], int m, int B[MAX], int n, int C[2*MAX], int *k)
que receba um vetor A de nmeros inteiros distintos e ordenados em ordem crescente de dimenso m e um vetor B de nmeros inteiros distintos e ordenados em ordem crescente de dimenso m e compute um vetor C contendo os elementos de A e de B sem repetio e em ordem crescente. (c) Escreva um programa que receba dois conjuntos de nmeros inteiros e distintos X e Y , com no mximo 100 elementos, ordene cada um deles usando a funo do item (a) e intercale esses dois vetores usando a funo do item (b), obtendo como resultado um vetor ordenado de nmeros inteiros distintos dois a dois.
DCT
UFMS
A ULA 37
F UNES
E MATRIZES
Na aula 36 vimos funes e vetores e destacamos como estas estruturas so tratadas de forma diferenciada neste caso. Em especial, muito importante lembrar que vetores so sempre parmetros passados por referncia s funes na linguagem C. Como veremos adiante, isso vale para qualquer varivel composta homognea, isto , para variveis compostas homogneas de qualquer dimenso.
37.1 Matrizes
Um elemento de uma matriz pode ser passado como parmetro para uma funo assim como zemos com uma varivel de um tipo bsico ou como um elemento de um vetor. Ou seja, a sentena
p = paridade(matriz[i][j]);
chama a funo paridade passando o valor contido em matriz[i][j] como um argumento para a funo. Uma matriz toda pode ser passada a uma funo da mesma forma que um vetor todo pode ser passado, bastando listar o identicador da matriz. Por exemplo, se a matriz A declarada como uma matriz bidimensional de nmeros inteiros, a sentena
multiplicaEscalar(A, escalar);
pode ser usada para chamar uma funo que multiplica cada elemento de uma matriz A pelo valor armazenado na varivel escalar . Assim como com vetores, uma modicao realizada no corpo de uma funo sobre qualquer elemento de uma matriz que um parmetro dessa funo provoca uma modicao no argumento da chamada da funo, ou seja, na matriz que foi passada funo durante sua chamada. O programa 37.1 recebe uma matriz A44 de nmeros inteiros e um escalar e realiza o produto da matriz por esse escalar. 236
37.2 M ATRIZES
237
#include <stdio.h> void multiplicaEscalar(int A[4][4], int escalar) { int i, j; for(i = 0; i < 4; i++) for(j = 0; j < 4; j++) A[i][j] = A[i][j] * escalar; return; } void imprimeMatriz(int A[4][4]) { int i, j; for(i = 0; i < 4; i++) { for(j = 0; j < 4; j++) printf("%3d ", A[i][j]); printf("\n"); } return; } int main(void) { int i, j, esc, matriz[4][4]; scanf("%d", &esc); for(i = 0; i < 4; i++) for(j = 0; j < 4; j++) scanf("%d", &matriz[i][j]); imprimeMatriz(matriz); multiplicaEscalar(matriz, esc); imprimeMatriz(matriz); return 0; }
DCT
UFMS
37.2 M ATRIZES
238
Exerccios
37.1 (a) Escreva uma funo com a seguinte interface:
void troca(int *a, int *b)
que receba dois nmeros inteiros a e b e troque os seus contedos. (b) Usando a funo anterior, escreva um programa que receba uma matriz de nmeros inteiros A, de dimenso m n, com 1 m, n 100, e dois nmeros inteiros i e j , troque os contedos das linhas i e j da matriz A e imprima a matriz resultante. Programa 37.2: Soluo do exerccio 37.1.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
#include <stdio.h> #define MAX 100 void troca(int *a, int *b) { int aux; aux = *a; *a = *b; *b = aux; return; } int main(void) { int m, n, i, j, k, A[MAX][MAX]; scanf("%d%d", &m, &n); for(i = 0; i < m; i++) for(j = 0; j < n; j++) scanf("%d", &A[i][j]); scanf("%d%d", &i, &j); for (k = 0; k < n; k++) troca(&A[i][k], &A[j][k]); for (i = 0; i < m; i++) { for (j = 0; j < n; j++) printf("%d ", A[i][j]); printf("\n"); } return 0; }
37.2 A k-sima potncia de uma matriz quadrada Ann , denotada por Ak , a matriz obtida a partir da matriz A da seguinte forma:
k
A = In A A A ,
DCT UFMS
37.2 M ATRIZES
239
onde In a matriz identidade de ordem n. Para facilitar a computao de Ak , podemos usar a seguinte frmula: Ak = In , se k = 0 , k 1 A A, se k > 0 .
que receba uma matriz I e uma dimenso n e preencha essa matriz com os valores da matriz identidade de ordem n. (b) Escreva uma funo com a seguinte interface:
void multMat(int C[MAX][MAX], int A[MAX][MAX], int B[MAX][MAX], int n)
que receba as matrizes A e B de ordem n, e copie os elementos da matriz B na matriz A. (d) Escreva um programa que, usando as funes em (a) e (b), receba uma matriz de nmeros inteiros A de dimenso n e um inteiro k e compute e imprima Ak . 37.3 Dizemos que uma matriz Ann um quadrado latino de ordem n se em cada linha e em cada coluna aparecem todos os inteiros 1, 2, 3, . . . , n, ou seja, cada linha e coluna permutao dos inteiros 1, 2, . . . , n. Exemplo: 1 2 4 3 2 3 1 4 3 4 2 1 4 1 3 2
A matriz acima um quadrado latino de ordem 4. (a) Escreva uma funo com a seguinte interface:
int linha(int A[MAX][MAX], int n, int i)
DCT
UFMS
37.2 M ATRIZES
240
que receba como parmetros uma matriz Ann de nmeros inteiros e um ndice i, e verique se na linha i de A ocorrem todos os nmeros inteiros de 1 a n, devolvendo 1 em caso positivo e 0 caso contrrio. (b) Escreva uma funo com a seguinte interface:
int coluna(int A[MAX][MAX], int n, int j)
que receba como parmetros uma matriz Ann de nmeros inteiros e um ndice j , e verique se na coluna j de A ocorrem todos os nmeros inteiros de 1 a n, devolvendo 1 em caso positivo e 0 caso contrrio. (c) Usando as funes dos itens (a) e (b), escreva um programa que verique se uma dada matriz Ann de nmeros inteiros, com 1 n 100, um quadrado latino de ordem n.
DCT
UFMS
A ULA 38
F UNES
E REGISTROS
J vimos funes com parmetros de tipos bsicos e de tipos complexos, como vetores e matrizes. Nesta aula, aprenderemos como comportam-se os registros no contexto das funes. Como veremos, um registro, diferentemente das variveis compostas homogneas, comportase como uma varivel de um tipo bsico qualquer quando trabalhamos com funes. Ou seja, um registro comporta-se como qualquer outra varivel excluindo as compostas homogneas, isto , um registro sempre um argumento passado por cpia a uma funo. Se quisermos que um registro seja um argumento passado por referncia a uma funo, devemos explicitamente indicar essa opo usando o smbolo & no argumento e o smbolo * no parmetro correspondente.
241
38.1 T IPO
REGISTRO
242
Observe que o caractere ponto e vrgula ( ; ) segue o caractere fecha chaves } . O ; deve estar presente neste ponto para terminar a declarao da etiqueta do registro. Uma vez que criamos a etiqueta cadastro , podemos us-la para declarar variveis, como a seguir:
Tambm, a declarao de uma etiqueta de um registro pode ser combinada com a declarao de variveis registros, como mostrado a seguir:
struct cadastro { int codigo; char nome[MAX+1]; int fone; } ficha1, ficha2;
Ou seja, acima temos a declarao da etiqueta de registro cadastro e a declarao de duas variveis registros ficha1 e ficha2 . Todas as variveis registros declaradas com o tipo struct cadastro so compatveis entre si e, portanto, podemos atribuir uma a outra sem problemas, com mostramos abaixo:
struct cadastro ficha1 = {10032, "Sir Lancelot", 77165115}; struct cadastro ficha2; ficha2 = ficha1;
Uma alternativa para declarao de etiquetas de registros o uso da palavra reservada typedef para denir o nome de um tipo genuno. Por exemplo, poderamos denir um tipo com nome Cadastro da seguinte forma:
DCT
UFMS
38.2 R EGISTROS
243
Observe que o nome do tipo, Cadastro , deve ocorrer no nal, no aps a palavra reservada struct . Alm disso, como Cadastro um nome de um tipo denido por typedef , no permitido escrever struct Cadastro . Todas as variveis do tipo Cadastro so compatveis, desconsiderando onde foram declaradas. Assim, a declarao de registros com essa denio pode ser dada como abaixo:
Quando precisamos declarar um nome para um registro, podemos escolher entre declarar uma etiqueta de registro ou denir um tipo registro com a palavra reservada typedef . Entretanto, em geral preferimos usar etiquetas de registros por diversos motivos que sero justicados posteriormente.
x = raizQuadrada(poligono.diagonal);
chama a funo raizQuadrada com argumento poligono.diagonal , isto , com o valor armazenado no campo diagonal do registro poligono . Um registro todo pode ser argumentos de funes da mesma forma que um vetor ou uma matriz podem ser, bastando listar o identicador do registro. Por exemplo, se temos um registro tempo , contendo os campos hora , minutos e segundos , ento a sentena
s = totalSegundos(tempo);
pode ser usada para chamar a funo totalSegundos que provavelmente calcula o total de segundos de uma medida de tempo armazenada no registro tempo . Diferentemente das variveis compostas homogneas, uma modicao realizada em qualquer campo de um registro que um parmetro de uma funo no provoca uma modicao no contedo do campo correspondente que um argumento da funo. Nesse caso, a princpio, um argumento que um registro em uma chamada de uma funo tem o mesmo comportamento de um argumento que uma varivel de um tipo bsico qualquer, ou seja, uma expresso que um argumento de uma chamada de uma funo e que, na verdade um registro, de fato um parmetro de entrada da funo, passado por cpia.
DCT UFMS
38.2 R EGISTROS
244
Um exemplo mostrado no programa 38.1. Programa 38.1: Um programa com uma funo com parmetro do tipo registro.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
#include <stdio.h> struct tipo_reg { int hh; int mm; int ss; }; int converteSegundos(struct tipo_reg tempo) { return tempo.hh * 3600 + tempo.mm * 60 + tempo.ss; } int main(void) { int segundos; struct tipo_reg horario; printf("Informe um horario (hh:mm:ss): "); scanf("%d:%d:%d", &horario.hh, &horario.mm, &horario.ss); segundos = converteSegundos(horario); printf("Passaram-se %d segundo(s) neste dia.\n", segundos); return 0; }
Devemos destacar pontos importantes neste programa. O primeiro, e talvez mais importante, a denio da etiqueta de registro tipo_reg . Em nenhuma outra oportunidade anterior havamos nos desenvolvido um programa que continha uma declarao de uma etiqueta de registro. Observe ainda que a declarao da etiqueta tipo_reg do programa 38.1 foi feita fora dos corpos das funes converteSegundos e main . Em todos os programas descritos at aqui, no temos um exemplo de uma declarao de uma etiqueta de registro, ou mesmo uma declarao de uma varivel de qualquer tipo, fora do corpo de qualquer funo. A declarao da etiqueta tipo_reg realizada dessa maneira se deve ao fato que a mesma usada nas funes converteSegundos e main , obrigando que essa declarao seja realizada globalmente, ou seja, fora do corpo de qualquer funo. Os outros pontos de destaque do programa 38.1 so todos relativos ao argumento da chamada da funo converteSegundos e do parmetro da mesma, descrito em sua interface. Note que essa funo tem como parmetro o registro tempo do tipo struct tipo_reg . O argumento da funo, na chamada dentro da funo main o registro horario de mesmo tipo struct tipo_reg . Finalmente, importante observar mais uma vez que a passagem de um registro para uma funo feita por cpia, ou seja, o parmetro correspondente um parmetro de entrada e, por isso, qualquer alterao realizada em um ou mais campos do registro dentro da funo no afeta o contedo dos campos do registro que um argumento na sua chamada. No exemplo acima no h alteraes nos campos do registro tempo no corpo da funo
DCT UFMS
38.3 R EGISTROS
245
converteSegundos , mas, caso houvesse, isso no afetaria qualquer um dos campos do registro horario , o argumento da chamada da funo converteSegundos no corpo da funo main .
#include <stdio.h> struct tipo_reg { int hh; int mm; int ss; }; void adicionaSegundo(struct tipo_reg *tempo) { (*tempo).ss++; if ((*tempo).ss == 60) { (*tempo).ss = 0; (*tempo).mm++; if ((*tempo).mm == 60) { (*tempo).mm = 0; (*tempo).hh++; if ((*tempo).hh == 24) (*tempo).hh = 0; } } return; } int main(void) { struct tipo_reg horario; printf("Informe um horario (hh:mm:ss): "); scanf("%d:%d:%d", &horario.hh, &horario.mm, &horario.ss); adicionaSegundo(&horario); printf("Novo horrio %02d:%02d:%02d.\n", horario.hh, horario.mm, horario.ss); return 0; }
DCT
UFMS
38.3 R EGISTROS
246
Uma observao importante sobre o programa 38.2 a forma como um registro que um parmetro passado por referncia apresentado no corpo da funo. Como o smbolo . que seleciona um campo de um registro tem prioridade sobre o smbolo * , que indica que o parmetro foi passado por referncia, os parnteses envolvendo o o smbolo * e o identicador do registro so essenciais para evitar erros. Dessa forma, uma expresso envolvendo, por exemplo, o campo ss do registro tempo , escrita como *tempo.ss , est incorreta e no realiza a seleo do campo do registro que tencionamos. Alternativamente, podemos usar os smbolos -> para indicar o acesso a um campo de um registro que foi passado por referncia a uma funo. O uso desse smbolo simplica bastante a escrita do corpo das funes que tm parmetros formais que so registros passados por referncia. Dessa forma, o programa 38.2 pode ser reescrito como o programa 38.3. Observe que esses dois programas tm a mesma nalidade, isto , dada uma entrada, realizam o mesmo processamento e devolvem as mesmas sadas. Programa 38.3: Uso do smbolo -> para registros passados por referncia.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
#include <stdio.h> struct tipo_reg { int hh; int mm; int ss; }; void adicionaSegundo(struct tipo_reg *tempo) { tempo->ss++; if (tempo->ss == 60) { tempo->ss = 0; tempo->mm++; if (tempo->mm == 60) { tempo->mm = 0; tempo->hh++; if (tempo->hh == 24) tempo->hh = 0; } } return; } int main(void) { struct tipo_reg horario; printf("Informe um horario (hh:mm:ss): "); scanf("%d:%d:%d", &horario.hh, &horario.mm, &horario.ss); adicionaSegundo(&horario); printf("Novo horrio %02d:%02d:%02d.\n", horario.hh, horario.mm, horario.ss); return 0; }
DCT
UFMS
38.4 F UNES
247
Observe que o argumento passado por referncia para a funo adicionaSegundo idntico nos programas 38.2 e 38.3. Mais que isso, a interface da funo adicionaSegundo idntica nos dois casos e o parmetro declarado como struct tipo_reg *tempo . No corpo desta funo do programa 38.3 que usamos o smbolo -> como uma abreviao, ou uma simplicao, da notao usada no corpo da funo adicionaSegundo do programa 38.2. Assim, por exemplo, a expresso (*tempo).ss no programa 38.2 reescrita como tempo->ss no programa 38.3.
DCT
UFMS
38.4 F UNES
248
#include <stdio.h> struct tipo_reg { int hh; int mm; int ss; }; struct tipo_reg adicionaSegundo(struct tipo_reg tempo) { struct tipo_reg atualizado; atualizado.ss = tempo.ss + 1; if (atualizado.ss == 60) { atualizado.ss = 0; atualizado.mm = tempo.mm + 1; if (atualizado.mm == 60) { atualizado.mm = 0; atualizado.hh = tempo.hh + 1; if (atualizado.hh == 24) atualizado.hh = 0; } else atualizado.hh = tempo.hh; } else { atualizado.mm = tempo.mm; atualizado.hh = tempo.hh; } return atualizado; } int main(void) { struct tipo_reg agora, novo; printf("Informe um horario (hh:mm:ss): "); scanf("%d:%d:%d", &agora.hh, &agora.mm, &agora.ss); novo = adicionaSegundo(agora); printf("Novo horrio %02d:%02d:%02d.\n", novo.hh, novo.mm, novo.ss); return 0; }
Exerccios
38.1 Este exerccio tem o mesmo objetivo do exerccio 27.1. (a) Escreva uma funo com a seguinte interface:
DCT
UFMS
38.4 F UNES
249
que receba uma data e verique se o ano bissexto, devolvendo 1 em caso positivo e 0 caso contrrio. Um ano bissexto se divisvel por 4 e no por 100 ou divisvel por 400. (b) Escreva uma funo com a seguinte interface:
int diasMes(struct data d)
que receba uma data e devolva o nmero de dias do ms em questo. Exemplo: Se a funo recebe a data 10/04/1992 deve devolver 30 e se recebe a data 20/02/2004 deve devolver 29 . (c) Escreva uma funo com a seguinte interface:
struct data diaSeguinte(struct data d)
que receba uma data e devolva a data que representa o dia seguinte. Use as funes dos itens (a) e (b). (d) Escreva um programa que receba uma data e imprima a data que representa o dia seguinte. Use as funes dos itens anteriores. 38.2 Reescreva a funo do item (c) do exerccio 38.1, fazendo com que a funo diaSeguinte tenha o registro d como um parmetro de entrada e sada. Isto , a interface da funo deve ser:
void diaSeguinte(struct data *d)
38.3
receba duas medidas de tempo no formato hh:mm:ss e calcule o tempo decorrido entre essas duas medidas de tempo. Tenha cuidado com um par de medidas de tempo que cruzam a meia noite. (b) Escreva um programa que receba dois horrios e calcule o tempo decorrido entre esses dois horrios. Use a funo anterior.
DCT
UFMS
A ULA 39
R ECURSO
O conceito de recurso tem importncia fundamental em computao. Na linguagem C, uma funo recursiva aquela que contm em seu corpo uma ou mais chamadas a si mesma. Naturalmente, uma funo deve possuir pelo menos uma chamada proveniente de uma outra funo externa. Uma funo no recursiva, em contrapartida, aquela para qual todas as suas chamadas so externas. Nesta aula construiremos funes recursivas para solues de problemas.
39.1 Denio
Em muitos problemas computacionais encontramos a seguinte propriedade: cada entrada do problema contm uma entrada menor do mesmo problema. Dessa forma, dizemos que esses problemas tm uma estrutura recursiva. Para resolver um problema como esse, usamos em geral a seguinte estratgia: se a entrada do problema pequena ento resolva-a diretamente; seno, reduza-a a uma entrada menor do mesmo problema, aplique este mtodo entrada menor e volte entrada original. A aplicao dessa estratgia produz um algoritmo recursivo, que implementado na linguagem C torna-se um programa recursivo ou um programa que contm uma ou mais funes recursivas. Uma funo recursiva aquela que possui, em seu corpo, uma ou mais chamadas a si mesma. Uma chamada de uma funo a si mesma dita uma chamada recursiva. Como sabemos, uma funo deve possuir ao menos uma chamada proveniente de uma outra funo, externa a ela. Se a funo s possui chamadas externas a si, ento chamada de funo no recursiva. At a aula de hoje construimos apenas funes no recursivas. Em geral, a toda funo recursiva corresponde uma outra no recursiva que executa exatamente a mesma computao. Como alguns problemas possuem estrutura recursiva natural, funes recursivas so facilmente construdas a partir de suas denies. Alm disso, a demonstrao da corretude de um programa recursivo facilitada pela relao direta entre sua estrutura e a induo matemtica. Outras vezes, no entanto, a implementao de um programa recursivo demanda um gasto maior de memria, j que durante seu processo de execuo muitas informaes devem ser guardadas na sua pilha de execuo. 250
39.2 E XEMPLOS
251
39.2 Exemplos
Um exemplo clssico de uma funo recursiva aquela que computa o fatorial de um nmero inteiro n 0. A idia da soluo atravs de uma recursiva baseada na frmula mostrada a seguir: 1, se n 1 , n! = n (n 1)! , caso contrrio . A funo recursiva fat , que calcula o fatorial de um dado nmero inteiro no negativo n, mostrada a seguir:
int fat(int n) { int result; if (n <= 1) result = 1; else result = n * fat(n-1); return result; }
A sentena return pode aparecer em qualquer ponto do corpo de uma funo e por isso podemos escrever a funo fat como a seguir:
Geralmente, preferimos a primeira verso implementada acima, onde existe apenas uma sentena com a palavra reservada return posicionada no nal do corpo da funo fat . Essa maneira de escrever funes recursivas evita confuso e nos permite seguir o uxo de execuo dessas funes mais naturalmente. No entanto, a segunda verso da funo fat apresentada acima equivalente primeira e isso signica que tambm vlida. Alm disso, essa segunda soluo mais compacta e usa menos memria, j que evita o uso de uma varivel. Por tudo isso, essa segunda forma de escrever funes recursivas muito usada por programadores(as) mais experientes. A execuo da funo fat se d da seguinte forma. Imagine que uma chamada fat(3) foi realizada. Ento, temos ilustrativamente a seguinte situao:
DCT
UFMS
39.2 E XEMPLOS
252
devolve 1 devolve 2 1 devolve 3 2 Repare nas indentaes que ilustram as chamadas recursivas. Vamos ver um prximo exemplo. Considere o problema de determinar um valor mximo de um vetor v com n elementos. O tamanho de uma entrada do problema n 1. Se n = 1 ento v[0] o nico elemento do vetor e portanto v[0] mximo. Se n > 1 ento o valor que procuramos o maior dentre o mximo do vetor v[0..n-2] e o valor armazenado em v[n-1] . Dessa forma, a entrada v[0..n-1] do problema ca reduzida entrada v[0..n-2] . A funo maximo a seguir implementa essa idia.
/* esta funo recebe um vetor de nmeros inteiros v e um nmero inteiro n >= 1 e devolve um elemento mximo no vetor v[0..n-1] */ int maximo(int v[MAX], int n) { int aux; if (n == 1) return v[0]; else { aux = maximo(v, n-1); if (aux > v[n-1]) return aux; else return v[n-1]; } }
Para vericar que uma funo recursiva est correta, devemos seguir o seguinte roteiro: Passo 1: escreva o que a funo deve fazer; Passo 2: verique se a funo de fato faz o que deveria fazer quando a entrada pequena; Passo 3: imagine que a entrada grande e suponha que a funo far a coisa certa para entradas menores; sob essa hiptese, verique que a funo faz o que dela se espera.
DCT
UFMS
39.2 E XEMPLOS
253
Exerccios
39.1 (a) Escreva uma funo no recursiva com a seguinte interface:
int pot(int x, int n)
que receba dois nmeros inteiros x e n e calcule e devolva xn . (b) Escreva uma funo recursiva com a seguinte interface:
int potR(int x, int n)
que receba dois nmeros inteiros x e n e calcule e devolva xn . Como no primeiro exemplo da seo anterior quando computamos o fatorial de um nmero, o valor de xn pode ser computado recursivamente basicamente da mesma forma, observando a seguinte frmula: xn = 1, se n = 0 , n 1 xx , se n > 1 . 0, e devolva
(c) Escreva um programa que receba dois nmeros inteiros x e n, com n xn . Use as funes em (a) e (b) para mostrar os dois resultados. 39.2 O que faz a funo abaixo?
Escreva um programa para testar a funo ib . 39.3 (a) Escreva uma funo recursiva que implementa o algoritmo de Euclides. Veja o exerccio 10.3. (b) Escreva um programa que receba dois nmeros inteiros e calcule o mximo divisor comum entre eles. Use a funo do item (a).
DCT
UFMS
39.2 E XEMPLOS
254
#include <stdio.h> int pot(int x, int n) { int i, result; result = 1; for (i = 1; i <= n; i++) result = result * x; return result; } int potR(int x, int n) { if (n == 0) return 1; else return x * potR(x, n-1); } int main(void) { int x, n; scanf("%d%d", &x, &n); printf("No resursiva: %d^%d = %d\n", x, n, pot(x, n)); printf("Resursiva : %d^%d = %d\n", x, n, potR(x, n)); return 0; }
DCT
UFMS
A ULA 40
E XERCCIOS
40.1
que receba um vetor v e um nmero inteiro n, tal que v contm n ponto utuante, e calcule e devolva a soma desses n nmeros.
1 nmeros com
(b) Usando a funo do item anterior, escreva um programa que receba um nmero inteiro n, com n 1, e mais n nmeros reais e calcule a soma desses nmeros. Programa 40.1: Soluo do exerccio 40.1.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
#include <stdio.h> #define MAX 100 float soma(float v[MAX], int n) { if (n == 1) return v[0]; else return soma(v, n-1) + v[n-1]; } int main(void) { int n, i; float V[MAX]; scanf("%d", &n); for (i = 0; i < n; i++) scanf("%f", &V[i]); printf("%g\n", soma(V, n)); return 0; }
255
256 40.2 (a) Escreva uma funo recursiva com a seguinte interface:
int somaDigitos(int n)
que receba um nmero inteiro positivo n e devolva a soma de seus dgitos. (b) Escreva um programa que receba um nmero inteiro n e imprima a soma de seus dgitos. Use a funo do item (a). 40.3 Como no exerccio 18.2, neste exerccio temos de imprimir a seqncia de Fibonacci. Essa seqncia dada pela seguinte frmula: F1 = 1 F = 1 2 Fi = Fi1 + Fi2 , para i 3. (a) Escreva uma funo recursiva com a seguinte interface:
int Fib(int i)
que receba um nmero inteiro positivo i e devolva o i-simo termo da seqncia de Fibonacci, isto , Fi . (b) Escreva um programa que receba um nmero inteiro i seqncia de Fibonacci. Use a funo do item (a). 1 e imprima o termo Fi da
40.4 Como no exerccio 20.3, neste exerccio temos computar a funo log2 n. Lembre-se que o piso de um nmero x o nico inteiro i tal que i x < i + 1. O piso de x denotado por x. Segue uma amostra de valores da funo log2 n: n log2 n 15 3 16 4 31 4 32 5 63 5 64 6 127 6 128 7 255 7 256 8 511 8 512 9
(b) Escreva um programa que receba um nmero inteiro n a funo do item (a).
40.5 Considere o seguinte processo para gerar uma seqncia de nmeros. Comece com um inteiro n. Se n par, divida por 2. Se n mpar, multiplique por 3 e some 1. Repita esse processo com o novo valor de n, terminando quando n = 1. Por exemplo, a seqncia de nmeros a seguir gerada para n = 22: 22
DCT
11
34
17
52
26
13
40
20
10
16
1
UFMS
257 conjecturado que esse processo termina com n = 1 para todo inteiro n > 0. Para uma entrada n, o comprimento do ciclo de n o nmero de elementos gerados na seqncia. No exemplo acima, o comprimento do ciclo de 22 16. (a) Escreva uma funo no recursiva com a seguinte interface:
int ciclo(int n)
que receba um nmero inteiro positivo n, mostre a seqncia gerada pelo processo descrito acima na sada e devolva o comprimento do ciclo de n. (b) Escreva uma verso recursiva da funo do item (a) com a seguinte interface:
int cicloR(int n)
que receba um nmero inteiro positivo n, mostre a seqncia gerada pelo processo descrito acima na sada e devolva o comprimento do ciclo de n. (c) Escreva um programa que receba um nmero inteiro n 1 e determine a seqncia gerada por esse processo e tambm o comprimento do ciclo de n. Use as funes em (a) e (b) para testar. 40.6 Podemos calcular a potncia xn de uma maneira mais eciente. Observe primeiro que se n uma potncia de 2 ento xn pode ser computada usando seqncias de quadrados. Por exemplo, x4 o quadrado de x2 e assim x4 pode ser computado usando somente duas multiplicaes ao invs de trs. Esta tcnica pode ser usada mesmo quando n no uma potncia de 2, usando a seguinte frmula: se n = 0, 1, n n/ 2 2 x = (40.1) (x ) , se n par, x xn1 , se n mpar. (a) Escreva uma funo com interface
int potencia(int x, int n)
que receba dois nmeros inteiros x e n e calcule e devolva xn usando a frmula (40.1). (b) Escreva um programa que receba dois nmeros inteiros a e b e imprima o valor de ab .
DCT
UFMS
A ULA 41
B USCA
O problema da busca de um elemento em um conjunto uma operao bsica em computao. A maneira como esse conjunto armazenado na memria do computador permite que algumas estratgias possam ser usadas para realizar a tarefa da busca. Na aula de hoje revemos os processos de busca que vimos usando corriqueiramente at o momento como uma sub-tarefa realizada na soluo de diversos problemas prticos. A busca ser xada, como antes, com os dados envolvidos como sendo nmeros inteiros. O conjunto de nmeros inteiros onde a busca se dar armazenado em um vetor. Alm de rever essa estratgia, chamada de busca seqencial, vemos ainda uma estratgia nova e muito eciente de busca em um vetor ordenado, chamada de busca binria.
/* Recebe um vetor v[0..n-1], com n >= 0, e um nmero x e devolve k no intervalo [0, n-1] tal que v[k] == x. Se tal k no existe, devolve -1. */ int busca(int v[MAX], int n, int x) { int k; for (k = n - 1; k >= 0 && v[k] != x; k--) ; return k; }
258
41.2 B USCA
EM UM VETOR ORDENADO
259
Observe como a funo eciente e elegante, funcionando corretamente mesmo quando o vetor est vazio, isto , quando n vale 0. Um exemplo de uma chamada funo busca apresentado abaixo:
A funo busca pode ser escrita em verso recursiva. A idia do cdigo simples: se n = 0 ento o vetor vazio e portanto x no est em v [0..n 1]; se n > 0 ento x est em v [0..n 1] se e somente se x = v [n 1] ou x est no vetor v [0..n 2]. A verso recursiva ento mostrada abaixo:
/* Recebe um vetor v[0..n-1], com n >= 0, e um nmero x e devolve k tal que 0 <= k < n e v[k] == x. Se tal k no existe, devolve -1. */ int buscaR(int v[MAX], int n, int x) { if (n == 0) return -1; else if (x == v[n - 1]) return n - 1; else return buscaR(v, n - 1, x); }
A corretude da funo busca foi mostrada no exerccio 20.1. Seu tempo de execuo linear no tamanho da entrada, isto , O(n), como mostrado na aula 32. A funo buscaR tem corretude e anlise de ecincia equivalentes.
Agora, em lugar de perguntar em que posio x est no vetor v [0..n 1], mais til e conveniente perguntar em que posio x deveria estar. Assim, o problema pode ser reformulado da seguinte maneira: dado um nmero inteiro x e um vetor de nmeros inteiros crescente v [0..n 1], encontrar um ndice k tal que v [k 1] < x
DCT
v [k] .
(41.1)
UFMS
41.2 B USCA
EM UM VETOR ORDENADO
260
De posse de tal ndice k, muito fcil resolver o problema da busca, bastando comparar x com v [k]. Observe ainda que qualquer valor de k no intervalo [0, n] pode ser uma soluo do problema da busca. Nos dois extremos do intervalo, a condio (41.1) deve ser interpretada adequadamente. Isto , se k = 0, a condio se reduz apenas a x v [0], pois v [1] no faz sentido. Se k = n, a condio se reduz somente a v [n 1] < x, pois v [n] no faz sentido. Tudo se passa como se o vetor v tivesse um componente imaginrio v [1] com valor e um componente imaginrio v [n] com valor +. Tambm, para simplicar um pouco o raciocnio, suporemos que n 1. Isso posto, observe ento que uma busca seqencial, recursiva ou no, pode ser realizada para resolver o problema, conforme apresentado na seo anterior. Vejamos ento a funo buscaOrd .
/* Recebe um vetor crescente v[0..n-1] com n >= 1 e um inteiro x e devolve um ndice k em [0, n] tal que v[k-1] < x <= v[k]. */ int buscaOrd(int v[MAX], int n, int x) { int k; for (k = 0; k < n && v[k] < x; k++) ; return k; }
Se aplicamos a estratgia da busca seqencial, isto , se aplicamos qualquer das duas funes apresentadas na seo anterior, para soluo do problema da busca sobre um vetor de entrada que se encontra ordenado, ento certamente obtemos uma resposta correta, porm ineciente do ponto de vista de seu consumo de tempo. Isso porque, no pior caso, a busca seqencial realiza a comparao do elemento x com cada um dos elementos do vetor v de entrada. Ou seja, o problema da busca resolvido em tempo proporcional ao nmero de elementos do vetor de entrada v , deixando de explorar sua propriedade especial de se encontrar ordenado. Com uma busca binria, podemos fazer o mesmo trabalho de forma bem mais eciente. A busca binria se baseia no mtodo que usamos s vezes para encontrar uma palavra no dicionrio: abrimos o dicionrio ao meio e comparamos a primeira palavra desta pgina com a palavra buscada. Se a primeira palavra menor que a palavra buscada, jogamos fora a primeira metade do dicionrio e repetimos a mesma estratgia considerando apenas a metade restante. Se, ao contrrio, a primeira palavra maior que a palavra buscada, jogamos fora a segunda metade do dicionrio e repetimos o processo. A funo buscaBin abaixo implementa essa idia.
DCT UFMS
41.2 B USCA
EM UM VETOR ORDENADO
261
/* Recebe um vetor crescente v[0..n-1] com n >= 1 e um inteiro x e devolve um ndice k em [0, n] tal que v[k-1] < x <= v[k]. */ int buscaBin(int v[MAX], int n, int x) { int e, d, m; e = -1; d = n; while (e < d - 1) { m = (e + d) / 2; if (v[m] < x) e = m; else d = m; } return d; }
k = buscaBin(v, n, x);
Para provar a correo da funo buscaBin , basta vericar o seguinte invariante: no incio de cada repetio while , imediatamente antes da comparao de e com d - 1 , vale a relao v[e] < x <= v[d] . Com esse invariante em mos, podemos usar a estratgia que aprendemos na aula 20 para mostrar nalmente que essa funo est de fato correta. Quantas iteraes a funo buscaBin executa? Essa conta revela o valor aproximado que representa o consumo de tempo dessa funo em um dado vetor de entrada. Observe que em cada iterao, o tamanho do vetor v dado por d e 1 . No incio da primeira iterao, o tamanho do vetor n. No incio da segunda iterao, o tamanho do vetor aproximadamente n/2. No incio da terceira, aproximadamente n/4. No incio da (k +1)-sima, aproximadamente n/2k . Quando k > log2 n, temos n/2k < 1 e a execuo da funo termina. Assim, o nmero de iteraes aproximadamente log2 n. O consumo de tempo da funo proporcional ao nmero de iteraes e portanto proporcional a log2 n. Esse consumo cresce com n muito mais lentamente que o consumo da busca seqencial. Uma soluo recursiva para o problema da busca em um vetor ordenado apresentada a seguir. Antes, necessrio reformular o problema ligeiramente. A funo recursiva buscaBinR procura o elemento x no vetor crescente v [e..d], supondo que o valor x est entre os extremos v [e] e v [d].
DCT
UFMS
41.2 B USCA
EM UM VETOR ORDENADO
262
/* Recebe um vetor crescente v[e..d] e um inteiro x tais que v[e] < x <= v[d] e devolve um ndice k em [e+1, d] tal que v[k-1] < x <= v[k]. */ int buscaBinR(int v[MAX], int e, int d, int x) { int m; if (e == d - 1) return d; else { m = (e + d) / 2; if (v[m] < x) return BuscaBinR(v, m, d, x); else return BuscaBinR(v, e, m, v); } }
Quando a funo buscaBinR chamada com argumentos (v, 1, n, x), ela chama a si mesma cerca de log2 n vezes. Este nmero de chamadas a profundidade da recurso.
Exerccios
41.1 Tome uma deciso de projeto diferente daquela da seo 41.1: se x no estiver em v [0..n 1], a funo deve devolver n. Escreva a verso correspondente da funo busca . Para evitar o grande nmero de comparaes de k com n, coloque uma sentinela em v [n].
1 2 3 4 5 6 7 8 9 10 11
/* Recebe um vetor v[0..n-1], com n >= 0, e um nmero x e devolve k no intervalo [0, n-1] tal que v[k] == x. Se tal k no existe, devolve n. */ int buscaS(int v[MAX], int n, int x) { int k; v[n] = x; for (k = 0; v[k] != x; k++) ; return k; }
DCT
UFMS
41.2 B USCA
EM UM VETOR ORDENADO
263
41.2 Considere o problema de determinar o valor de um elemento mximo de um vetor v [0..n 1]. Considere a funo maximo abaixo.
1 2 3 4 5 6 7 8 9
int maximo(int v[MAX], int n) { int i, x; x = v[0]; for (i = 1; i < n; i++) if (x < v[i]) x = v[i]; return x; }
(a) A funo maximo acima resolve o problema? (b) Faz sentido trocar x = v[0] pode x = 0 ? (c) Faz sentido trocar x = v[0] pode x = INT_MIN 1 ? (d) Faz sentido trocar x < v[i] por x <= v[i] ? 41.3 O autor da funo abaixo arma que ela decide se x est no vetor v [0..n 1]. Critique seu cdigo.
1 2 3 4 5 6 7
int busc(int v[MAX], int n, int x) { if (v[n-1] == x) return 1; else return busc(v, n-1, x); }
41.4 A operao de remoo consiste de retirar do vetor v [0..n 1] o elemento que tem ndice k e fazer com que o vetor resultante tenha ndices 0, 1, . . . , n 2. Essa operao s faz sentido se 0 k < n. (a) Escreva uma funo iterativa com a seguinte interface:
int remove(int v[MAX], int n, int k)
que remove o elemento de ndice k do vetor v [0..n 1] e devolve o novo valor de n, supondo que 0 k < n. (b) Escreva uma funo recursiva para a remoo com a seguinte interface:
1
DCT
41.2 B USCA
EM UM VETOR ORDENADO
264
41.5 A operao de insero consiste em introduzir um novo elemento y entre a posio de ndice k 1 e a posio de ndice k no vetor v [0..n 1], com 0 k n. (a) Escreva uma funo iterativa com a seguinte interface:
int insere(int v[MAX], int n, int k, int y)
que insere o elemento y entre as posies k 1 e k do vetor v [0..n 1] e devolve o novo valor de n, supondo que 0 k n. (b) Escreva uma funo recursiva para a insero com a seguinte interface:
int insereR(int v[MAX], int n, int k, int x)
41.6 Na busca binria, suponha que v [i] = i para todo i. (a) Execute a funo buscaBin com n = 9 e x = 3; (b) Execute a funo buscaBin com n = 14 e x = 7; (c) Execute a funo buscaBin com n = 15 e x = 7. 41.7 Execute a funo buscaBin com n = 16. Quais os possveis valores de m na primeira iterao? Quais os possveis valores de m na segunda iterao? Na terceira? Na quarta? 41.8 Conra a validade da seguinte armao: quando n + 1 uma potncia de 2, o valor da expresso (e + d) divisvel por 2 em todas as iteraes da funo buscaBin , quaisquer que sejam v e x. 41.9 Responda as seguintes perguntas sobre a funo buscaBin . (a) Que acontece se while (e < d - 1) for substitudo por while (e < d) ? (b) Que acontece se if (v[m] < x) for substitudo por if (v[m] <= x) ? (c) Que acontece se e = m for substitudo por e = m+1 ou por e = m - 1 ? (d) Que acontece se d = m for substitudo por d = m + 1 ou por d = m - 1 ? 41.10 Se t segundos so necessrios para fazer uma busca binria em um vetor com n elementos, quantos segundos sero necessrios para fazer uma busca em n2 elementos? 41.11 Escreva uma verso da busca binria para resolver o seguinte problema: dado um inteiro x e um vetor decrescente v [0..n 1], encontrar k tal que v [k 1] > x v [k].
DCT
UFMS
41.2 B USCA
EM UM VETOR ORDENADO
265
41.12 Suponha que cada elemento do vetor v [0..n 1] uma cadeia de caracteres (ou seja, temos uma matriz de caracteres). Suponha tambm que o vetor est em ordem lexicogrca. Escreva uma funo eciente, baseada na busca binria, que receba uma cadeia de caracteres x e devolva um ndice k tal que x igual a v [k]. Se tal k no existe, a funo deve devolver 1. 41.13 Suponha que cada elemento do vetor v [0..n 1] um registro com dois campos: o nome do(a) estudante e o nmero do(a) estudante. Suponha que o vetor est em ordem crescente de nmeros. Escreva uma funo de busca binria que receba o nmero de um(a) estudante e devolva seu nome. Se o nmero no estiver no vetor, a funo deve devolver a cadeia de caracteres vazia. 41.14 Escreva uma funo que receba um vetor estritamente crescente v [0..n 1] de nmeros inteiros e devolva um ndice i entre 0 e n 1 tal que v [i] = i. Se tal i no existe, a funo deve devolver 1. A sua funo no deve fazer mais que log2 n comparaes envolvendo os elementos de v .
DCT
UFMS
A ULA 42
O RDENAO :
MTODOS ELEMENTARES
Alm da busca, a ordenao outra operao elementar em computao. Nesta aula revisaremos os mtodos de ordenao elementares que j tivemos contato em aulas anteriores: o mtodo das trocas sucessivas, da seleo e da insero. Esses mtodos foram projetados a partir de idias simples e tm, como caracterstica comum, tempo de execuo de pior caso quadrtico no tamanho da entrada.
1 2 3 4 5 6 7 8
void ordTrocas(int v[], int n) { int i, j; for (i = n - 1; i > 0; i--) for (j = 0; j < i; j++) if (v[j] > v[j+1]) troca(&v[j], &v[j+1]); }
266
42.3 M TODO
DA SELEO
267
ordTrocas(v, n);
Para entender a funo ordTrocas basta observar que no incio de cada repetio do for externo vale que: o vetor v [0..n 1] uma permutao do vetor original, o vetor v [i + 1..n 1] crescente e v [j ] v [i + 1] para j = 0, 1, . . . , i.
Alm disso, o consumo de tempo da funo ordTrocas proporcional ao nmero de execues da comparao v [j ] > v [j + 1], que proporcional a n2 no pior caso.
1 2 3 4 5 6 7 8 9 10 11
void ordSelecao(int v[], int n) { int i, j, min; for (i = 0; i < n-1; i++) { min = i; for (j = i+1; j < n; j++) if (v[j] < v[min]) min = j; troca(&v[i], &v[min]); } }
ordSelecao(v, n);
Para entender como e por que o a funo ordSelecao funciona, basta observar que no incio de cada repetio do for externo valem os seguintes invariantes: o vetor v [0..n 1] uma permutao do vetor original,
DCT UFMS
42.4 M TODO
DA INSERO
268
O terceiro invariante pode ser assim interpretado: v [0..i 1] contm todos os elementos pequenos do vetor original e v [i..n 1] contm todos os elementos grandes. Os trs invariantes garantem que no incio de cada iterao os elementos v [0], . . . , v [i 1] j esto em suas posies denitivas. Uma anlise semelhante que zemos para a funo ordTrocas mostra que o mtodo da insero implementado pela funo ordSelecao consome n2 unidades de tempo no pior caso.
1 2 3 4 5 6 7 8 9 10
void ordInsercao(int v[], int n) { int i, j, x;; for (i = 1; i < n; i++) { x = v[i]; for (j = i-1; j >= 0 && v[j] > x; j--) v[j+1] = v[j]; v[j+1] = x; } }
ordInsercao(v, n);
Para entender a funo ordInsercao basta observar que no incio de cada repetio do for externo, valem os seguintes invariantes: o vetor v [0..n 1] uma permutao do vetor original e o vetor v [0..i 1] crescente. O consumo de tempo da funo ordInsercao proporcional ao nmero de execues da comparao v [j ] > x, que proporcional a n2 .
DCT UFMS
42.4 M TODO
DA INSERO
269
Exerccios
42.1 Escreva uma funo que verique se um dado vetor v [0..n 1] crescente.
1 2 3 4 5 6 7
int verifOrd(int v[], int n) { for (i = 0; i < n-1; i++) if (v[i] > v[i+1]) return 0; return 1; }
42.2 Que acontece se trocarmos i > 0 por i >= 0 no cdigo da funo ordTrocas ? Que acontece se trocarmos j < i por j <= i ? 42.3 Troque v[j] > v[j+1] por v[j] >= v[j+1] no cdigo da funo ordTrocas . A nova funo continua produzindo uma ordenao crescente de v [0..n 1]? 42.4 Escreva uma verso recursiva do mtodo de ordenao por trocas sucessivas. 42.5 Que acontece se trocarmos i = 0 por i = 1 no cdigo da funo ordSelecao ? Que acontece se trocarmos i < n-1 por i < n ? 42.6 Troque v[j] < v[min] por v[j] <= v[min] no cdigo da funo ordSelecao . A nova funo continua produzindo uma ordenao crescente de v [0..n 1]? 42.7 Escreva uma verso recursiva do mtodo de ordenao por seleo. 42.8 No cdigo da funo ordInsercao , troque v[j] > x por v[j] >= x . A nova funo continua produzindo uma ordenao crescente de v [0..n 1]? 42.9 No cdigo da funo ordInsercao , que acontece se trocarmos i = 1 por i = 0 ? Que acontece se trocarmos v[j+1] = x por v[j] = x ? 42.10 Escreva uma verso recursiva do mtodo de ordenao por insero. 42.11 Escreva uma funo que rearranje um vetor v [0..n 1] de modo que ele que em ordem estritamente crescente. 42.12 Escreva uma funo que permute os elementos de um vetor v [0..n 1] de modo que eles quem em ordem decrescente.
DCT
UFMS
A ULA 43
O RDENAO
POR INTERCALAO
Na aula 42 revimos os mtodos de ordenao mais bsicos, que so todos iterativos, simples e tm tempo de execuo de pior caso proporcional a n2 , onde n o tamanho da entrada. Mtodos mais ecientes de ordenao so baseados em recurso, tcnica introduzida na aula 39. Nesta aula, estudamos o mtodo da ordenao por intercalao, conhecido como mergesort. A ordenao por intercalao, que veremos nesta aula, e a ordenao por separao, que veremos na aula 44, so mtodos ecientes baseados na tcnica recursiva chamada dividir para conquistar, onde quebramos o problema em vrios sub-problemas de menor tamanho que so similares ao problema original, resolvemos esses sub-problemas recursivamente e ento combinamos essas solues para produzir uma soluo para o problema original.
43.2 P ROBLEMA
DA INTERCALAO
271
O problema da intercalao que queremos resolver aqui mais especco e pode ser assim descrito: dados dois vetores crescentes v [p..q 1] e v [q..r 1], rearranjar v [p..r 1] em ordem crescente. Isso signica que queremos de alguma forma intercalar os vetores v [0..q 1] e v [q..r 1]. Nesse caso, a primeira vista parece que os vetores de entrada pode ter elementos em comum. Entretanto, este no o caso, j que estamos considerando o mesmo conjunto inicial de elementos armazenados no vetor v . Uma maneira fcil de resolver o problema da intercalao usar um dos mtodos de ordenao da aula 42 tendo como entrada o vetor v [p..r 1]. Essa soluo, no entanto, tem consumo de tempo de pior caso proporcional ao quadrado do nmero de elementos do vetor e ineciente por desconsiderar as caractersticas dos vetores v [p..q 1] e v [q..r 1]. Uma soluo mais eciente, que usa um vetor auxiliar, mostrada a seguir.
permite ou no elementos iguais nos dois conjuntos de entrada, isto , conjuntos de entrada A e B tais que A B = ou A B = .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
/* Recebe os vetores crescentes v[p..q-1] e v[q..r-1] e rearranja v[p..r-1] em ordem crescente */ void intercala(int v[MAX], int p, int q, int r) { int w[MAX], i, j, k; i = p; j = q; k = 0; while (i < q && j < r) { if (v[i] < v[j]) { w[k] = v[i]; i++; } else { w[k] = v[j]; j++; } k++; } while (i < q) { w[k] = v[i]; i++; k++; } while (j < r) { w[k] = v[j]; j++; k++; } for (i = p; i < r; i++) v[i] = w[i-p]; }
DCT
UFMS
43.3 O RDENAO
POR INTERCALAO
272
A funo intercala tem tempo de execuo de pior caso proporcional ao nmero de comparaes entre os elementos do vetor, isto , r p. Assim, podemos dizer que o consumo de tempo no pior caso da funo intercala proporcional ao nmero de elementos do vetor de entrada.
1 2 3 4 5 6 7 8 9 10 11
/* recebe um vetor v[p..r-1] e o rearranja em ordem crescente */ void mergesort(int v[MAX], int p, int r) { int q; if (p < r - 1) { q = (p + r) / 2; mergesort(v, p, q); mergesort(v, q, r); intercala(v, p, q, r); } }
Como a expresso (p + q )/2 da funo mergesort do tipo inteiro, observe que seu resulq tado , na verdade, avaliado como p+ 2 . Observe tambm que para ordenar um vetor v [0..n 1] basta chamar a funo mergesort com os seguintes argumentos:
mergesort(v, 0, n);
Vejamos um exemplo de execuo da funo mergesort na gura 43.1, para um vetor de entrada v [0..7] = {4, 6, 7, 3, 5, 1, 2, 8} e chamada
mergesort(v, 0, 8);
Observe que as chamadas recursivas so realizadas at a linha divisria imaginria ilustrada na gura, quando p r 1. A partir desse ponto, a cada volta de um nvel de recurso,
DCT UFMS
43.3 O RDENAO
POR INTERCALAO
273
uma intercalao realizada. No nal, uma ltima intercalao realizada e o vetor original torna-se ento um vetor crescente com os mesmos elementos de entrada.
Figura 43.1: Exemplo de execuo da ordenao por intercalao. Qual o desempenho da funo mergesort quando queremos ordenar um vetor v [0..n 1]? Suponha, para efeito de simplicao, que n uma potncia de 2. Se esse no o caso, podemos examinar duas potncias de 2 consecutivas, justamente aquelas tais que 2k1 < n 2k , para algum k 0. Observe ento que o nmero de elementos do vetor diminudo a aproximadamente metade a cada chamada da funo mergesort . Ou seja, o nmero aproximado de chamadas proporcional a log2 n. Na primeira vez, o problema original reduzido a dois n sub-problemas onde necessrio ordenar os vetores v [0.. n 2 1] e v [ 2 ..n 1]. Na segunda vez, cada um dos sub-problemas so ainda divididos em mais dois sub-problemas cada, gerando n n quatro sub-problemas no total, onde necessrio ordenar os vetores v [0.. n 4 1], v [ 4 .. 2 1], n 3n 3n v [ 2 .. 4 1] e v [ 4 ..n 1]. E assim por diante. Alm disso, como j vimos, o tempo total que a funo intercala gasta proporcional a n. Portanto, a funo mergesort consome tempo proporcional a n log2 n.
DCT
UFMS
43.3 O RDENAO
POR INTERCALAO
274
Exerccios
43.1 Simule detalhadamente a execuo da funo mergesort sobre o vetor de entrada v [0..7] = {3, 41, 52, 26, 38, 57, 9, 49}. 43.2 A funo intercala est correta nos casos extremos p = q e q = r ? 43.3 Um algoritmo de intercalao estvel se no altera a posio relativa dos elementos que tm um mesmo valor. Por exemplo, se o vetor tiver dois elementos de valor 222, um algoritmo de intercalao estvel manter o primeiro 222 antes do segundo. A funo intercala estvel? Se a comparao v[i] <= v[j] for trocada por v[i] < v[j] a funo ca estvel? 43.4 O que acontece se trocarmos (p + r)/2 por (p + r - 1)/2 no cdigo da funo mergesort ? Que acontece se trocarmos (p + r)/2 por (p + r + 1)/2 ? 43.5 Escreva uma verso da ordenao por intercalao que rearranje um vetor v [p..r 1] em ordem decrescente. 43.6 Escreva uma funo eciente que receba um conjunto S de n nmeros reais e um nmero real x e determine se existe um par de elementos em S cuja soma exatamente X . 43.7 Seja A um vetor de n nmeros inteiros distintos. Se i < j e A[i] > A[j ] ento o par (i, j ) chamado uma inverso de A. (b) Qual vetor com elementos do conjunto {1, 2, . . . , n} tem o maior nmero de inverses? Quantas so? (c) Qual a relao entre o tempo de execuo da ordenao por insero e o nmero de inverses em um vetor de entrada? Justique sua resposta. (d) Modicando a ordenao por intercalao, escreva uma funo eciente que determine o nmero de inverses em uma permutao de n elementos. 43.8 Escreva um programa para comparar experimentalmente o desempenho da funo mergesort com o das funes ordTrocas , ordSelecao e ordInsercao da aula 42. Use um vetor aleatrio para fazer os testes. 43.9 Veja animaes dos mtodos de ordenao que j vimos nas seguintes pginas: Sorting Algorithms de J. Harrison; Sorting Algorithms de P. Morin; Sorting Algorithms Animations de D. R. Martin. (a) Liste as cinco inverses do vetor {2, 3, 8, 6, 1}.
DCT
UFMS
A ULA 44
O RDENAO
POR SEPARAO
O mtodo de ordenao por separao resolve o problema da ordenao descrito na aula 42, rearranjando um vetor v [0..n 1] de modo que se torne crescente. Em geral, este mtodo muito mais rpido que os mtodos elementares vistos na aula 42, mas pode ser to lento quanto aqueles para certas entradas especiais do problema. O mtodo da ordenao por separao recursivo e tambm se baseia na estratgia de dividir para conquistar. O mtodo comumente conhecido como quisksort. freqentemente usado na prtica para ordenao j que destacadamente rpido na mdia. Apenas para algumas entradas especiais o mtodo to lento quanto os mtodos elementares de ordenao.
275
44.1 P ROBLEMA
DA SEPARAO
276
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
int separa(int v[], int p, int r) { int x, i, j; x = v[p]; i = p - 1; j = r + 1; while (1) { do j--; while (v[j] > x); do i++; while (v[i] < x); if (i < j) troca(&v[i], &v[j]); else return j; } }
v [p..r ]
5 3 2 6 4 1 3 7 5 3 2 6 4 1 3 7
(a)
(b)
(c)
j v [p..q ]
3 3 2 1 4
i v [q + 1..r ]
6 5 7
(d)
j
(e)
44.2 O RDENAO
POR SEPARAO
277
O corpo da estrutura de repetio while da funo separa repetido at que i j e, neste ponto, o vetor v [p..r ] acabou de ser separado em v [p..q ] e v [q + 1..r ], com p q < r , tal que nenhum elemento de v [p..q ] maior que um elemento de v [q + 1..r ]. O valor q = j devolvido ento pela funo separa . Em outras palavras, podemos dizer que no incio de cada iterao valem os seguintes invariantes: v [p..r ] uma permutao do vetor original, v [p..i] p i x j v [j..r ] e r.
O nmero de iteraes que a funo realiza proporcional a r p + 1, isto , proporcional ao nmero de elementos do vetor.
1 2 3 4 5 6 7 8 9
void quicksort(int v[], int p, int r) { int q; if (p < r) { q = separa(v, p, r); quicksort(v, p, q); quicksort(v, q+1, r); } }
Uma chamada da funo quicksort para ordenao de um vetor v [0..n 1] deve ser feita como a seguir:
quicksort(v, 0, n-1);
Observe ainda que a funo quicksort est correta mesmo quando p > r , isto , quando o vetor est vazio. O consumo de tempo do mtodo de ordenao por separao proporcional ao nmero de comparaes realizadas entre os elementos do vetor. Se o ndice devolvido pela funo
DCT UFMS
44.2 O RDENAO
POR SEPARAO
278
separa sempre tiver valor mais ou menos mdio de p e r , ento o nmero de comparaes
ser aproximadamente n log2 n. Caso contrrio, o nmero de comparaes ser da ordem de n2 . Observe que isso ocorre, por exemplo, quando o vetor j estiver ordenado ou quase-ordenado. Portanto, o consumo de tempo de pior caso da ordenao por separao no melhor que o dos mtodos elementares vistos na aula 42. Felizmente, o pior caso para a ordenao por separao raro. Dessa forma, o consumo de tempo mdio da funo quicksort proporcional a n log2 n.
Exerccios
44.1 Ilustre a operao da funo separa sobre o vetor v que contm os elementos do conjunto {13, 19, 9, 5, 12, 8, 7, 4, 11, 2, 6, 21}. 44.2 Qual o valor de q a funo separa devolve quando todos os elementos no vetor v [p..r ] tm o mesmo valor? 44.3 A funo separa produz o resultado correto quando p = r ? 44.4 Escreva uma funo que rearranje um vetor v [p..r ] de nmeros inteiros de modo que os elementos negativos e nulos quem esquerda e os positivos quem direita. Em outras palavras, rearranje o vetor de modo que tenhamos v [p..q 1] 0 e v [q..r ] > 0 para algum q em p..r + 1. Procure escrever uma funo eciente que no use um vetor auxiliar. 44.5 Digamos que um vetor v [p..r ] est arrumado se existe q em p..r que satisfaz v [p..q 1] v [q ] < v [q + 1..r ] .
Escreva uma funo que decida se v [p..r ] est arrumado. Em caso armativo, sua funo deve devolver o valor de q . 44.6 Que acontece se trocarmos if (p < r) por if (p != r) no corpo da funo quicksort ? 44.7 Compara as funes quicksort e mergesort . Discuta as semelhanas e diferenas. 44.8 Como voc modicaria a funo quicksort para ordenar elementos em ordem decrescente? 44.9 Os bancos freqentemente gravam transaes sobre uma conta corrente na ordem das datas das transaes, mas muitas pessoas preferem receber seus extratos bancrios em listagens ordenadas pelo nmero do cheque emitido. As pessoas em geral emitem cheques na ordem da numerao dos cheques, e comerciantes usualmente descontam estes cheques com rapidez razovel. O problema de converter uma lista ordenada por data de transao em uma lista ordenada por nmero de cheques portanto o problema de ordenar uma entrada quase j ordenada. Argumente que, neste caso, o mtodo de ordenao por insero provavelmente se comportar melhor do que o mtodo de ordenao por separao. 44.10 Fornea um argumento cuidadoso para mostrar que a funo separa correta. Prove o seguinte:
DCT UFMS
44.2 O RDENAO
POR SEPARAO
279
(a) Os ndices i e j nunca referenciam um elemento de v fora do intervalo [p..r ]; (b) O ndice j no igual a r quando separa termina (ou seja, a partio sempre no trivial); (c) Todo elemento de v [p..j ] menor ou igual a todo elemento de v [j + 1..r ] quando a funo separa termina. 44.11 Escreva uma verso da funo quicksort que coloque um vetor de cadeias de caracteres em ordem lexicogrca. 44.12 Considere a seguinte soluo do problema da separao, devido a N. Lomuto. Para separar v [p..r ], esta verso incrementa duas regies, v [p..i] e v [i + 1..j ], tal que todo elemento na primeira regio menor ou igual a x = v [r ] e todo elemento na segunda regio maior que x.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
int separaLomuto(int v[], int p, int r) { int x, i, j; x = v[r]; i = p - 1; for (j = p; j <= r; j++) if (v[j] <= x) { i++; troca(&v[i], &v[j]); } if (i < r) return i; else return i - 1; }
(a) Ilustre a operao da funo separaLomuto sobre o vetor v que contm os elementos {13, 19, 9, 5, 12, 8, 7, 4, 11, 2, 6, 21}. (c) Qual o nmero mximo de vezes que um elemento pode ser movido pelas funes separa e separaLomuto ? Justique sua resposta.
(d) Argumente que separaLomuto , assim como separa , tem tempo de execuo proporcional a n sobre um vetor de n = r p + 1 elementos.
(e) Como a troca da funo separa pela funo separaLomuto afeta o tempo de execuo do mtodo da ordenao por separao quando todos os valores de entrada so iguais?
44.13 A funo quicksort contm duas chamadas recursivas para ela prpria. Depois da chamada da separa , o sub-vetor esquerdo ordenado recursivamente e ento o sub-vetor direito ordenado recursivamente. A segunda chamada recursiva no corpo da funo quicksort no realmente necessria; ela pode ser evitada usando uma estrutura de
DCT UFMS
44.2 O RDENAO
POR SEPARAO
280
controle iterativa. Essa tcnica, chamada recurso de cauda, fornecida automaticamente por bons compiladores. Considere a seguinte verso da ordenao por separao , que simula a recurso de cauda.
1 2 3 4 5 6 7 8
void quicksort2(int v[], int p, int r) { while (p < r) { q = separa(v, p, r); quicksort2(v, p, q); p = q + 1; } }
Ilustre a operao da funo quicksort2 sobre o vetor v que contm os elementos {21, 7, 5, 11, 6, 42, 13, 2}. Use a chamada quicksort2(v, 0, n-1) . Argumente que a funo quicksort2 ordena corretamente o vetor v . 44.14 Um famoso programador props o seguinte mtodo de ordenao de um vetor v [0..n 1] de nmeros inteiros:
1 2 3 4 5 6 7 8 9 10 11 12
void ordPateta(int v[], int i, int j) { int k; if (v[i] > v[j]) troca(&v[i], &v[j]); if (i + 1 < j) { k = (j - i + 1) / 3; ordPateta(v, i, j-k); ordPateta(v, i+k, j); ordPateta(v, i, j-k); } }
Ilustre a operao desse novo mtodo de ordenao sobre o vetor v que contm os elementos {21, 7, 5, 11, 6, 42, 13, 2}. Use a chamada ordPateta(v, 0, n-1) . Argumente que a funo ordPateta ordena corretamente o vetor v . 44.15 Veja animaes dos mtodos de ordenao que j vimos nas seguintes pginas: Sorting Algorithms de J. Harrison; Sorting Algorithms de P. Morin; Sorting Algorithms Animations de D. R. Martin. 44.16 Familiarize-se com a funo qsort da biblioteca stdlib da linguagem C.
DCT
UFMS
A ULA 45
B IBLIOTECA
PADRO
Nas ltimas aulas aprendemos a construir nossas prprias funes. Os benefcios dessa prtica so muitos, como percebemos. Entre eles, podemos destacar abstrao, organizao e reaproveitamento de cdigo, por exemplo. Felizmente, no sempre que precisamos de um trecho de cdigo que realiza um determinado processamento que temos necessariamente de programar esse trecho, construindo uma funo. A linguagem C fornece uma biblioteca padro de funes que nos ajuda em muitas tarefas importantes, desde funes de entrada e sada de dados, manipulao de cadeias de caracteres at funes matemticas. Nesta aula, veremos algumas funes importantes, destacando que muitas outras no sero cobertas aqui. Os interessados em mais informaes devem buscar a pgina do guia de referncia da biblioteca da linguagem C. Nesta aula, cobrimos com algum detalhe os arquivos que contm denies de macros, tipos e os prottipos das funes da biblioteca padro de funes da linguagem C. Esses arquivos so tambm conhecidos como arquivos-cabealhos e tm extenso .h, do ingls header. Iniciamos com uma viso sobre os qualicadores de tipos existentes na linguagem C, fundamentais especialmente na declarao de parmetros sensveis alterao, tais como os vetores e as matrizes, que so sempre parmetros por referncia. Em seguida, denimos arquivoscabealhos para, por m, cobrir os arquivos-cabealhos da biblioteca padro da linguagem C. Esta aula um guia de referncia da biblioteca padro da linguagem C, suas constantes, tipos e funes principais. Algumas funes, por serem muito especcas, no foram listadas aqui.
282
cria um objeto const do tipo int com identicador n cujo valor 10 . A declarao abaixo:
cria um vetor const de nmeros inteiros e identicador v . Muitas vantagens podem ser destacadas quando declaramos um objeto do tipo const , como por exemplo auto-documentao e vericao pelo compilador de tentativas alterao. A primeira vista, pode parecer que o qualicador de tipo const tem o mesmo papel da diretiva #define , que foi usada em aulas anteriores para denir nomes para constantes ou macros. Existem diferenas signicativas entre as duas, que so listadas abaixo: podemos usar #define para criar um nome par uma constante numrica, caractere ou cadeia de caracteres; por outro lado, podemos usar const para criar objetos somente para leitura de qualquer tipo, incluindo vetores, estruturas, unies, apontadores, etc; objetos criados com const esto sujeitos s mesmas regras de escopo de qualquer varivel, mas constantes criadas com #define no; o valor de um objeto criado com const pode ser visto em um depurador de programas; o valor de uma macro no pode; objetos criados com const no podem ser usados em expresses constantes; as constantes criadas com #define podem; podemos aplicar o operador de endereamento & sobre um objeto const , j que ele possui um endereo; uma macro no tem um endereo. No h uma regra bem denida que determina o uso de #define ou const na declarao de constantes. Em geral, usamos a diretiva #define mais especicamente para criar constantes que representam nmeros ou caracteres.
45.2 Arquivo-cabealho
A diretiva #include ordena o pr-processador a abrir um arquivo especicado e a inserir seu contedo no arquivo atual. Assim, se queremos que vrios arquivos de cdigo fonte tenham acesso mesma informao, devemos colocar essa informao em um arquivo e ento usar a diretiva #include para trazer o contedo do arquivo em cada arquivo de cdigo fonte. Arquivos que so includos dessa forma so chamados de arquivos-cabealhos, do ingls header les ou tambm include les. Por conveno, um arquivo como esse tem a extenso .h .
DCT UFMS
283
A diretiva #include pode ser usada de duas formas. A primeira forma usada para arquivos-cabealhos que pertencem biblioteca padro da linguagem C:
#include <arquivo.h>
A segunda forma usada para todos os outros arquivos-cabealhos, incluindo aqueles que so escritos por programadores(as):
#include "arquivo.h"
A diferena entre as duas formas se d pela maneira como o compilador busca o arquivocabealho. Na primeira, o compilador busca o arquivo-cabealho no(s) diretrio(s) em que os arquivos-cabealhos do sistema se encontram. Nos sistemas baseados no UNIX, como o LINUX, os arquivos-cabealhos so mantidos usualmente no diretrio /usr/include . Na segunda forma, a busca realizada no diretrio corrente e, em seguida, no(s) diretrio(s) em que os arquivos-cabealhos do sistema se encontram. Arquivos-cabealhos auxiliam no compartilhamento de denies de macros, de tipos e de prottipos de funes por dois ou mais arquivos de cdigo fonte. Abaixo apresentamos o arquivo-cabealho completo assert.h da biblioteca padro da linguagem C.
/* Copyright (C) 1991,1992,1994-2001,2003,2004,2007 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the GNU C Library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ /* *
DCT
<assert.h>
UFMS
284
*/ #ifdef _ASSERT_H
# undef _ASSERT_H # undef assert # undef __ASSERT_VOID_CAST # ifdef __USE_GNU # undef assert_perror # endif #endif /* assert.h #define _ASSERT_H #include <features.h> */ 1
#if defined __cplusplus && __GNUC_PREREQ (2,95) # define __ASSERT_VOID_CAST static_cast<void> #else # define __ASSERT_VOID_CAST (void) #endif /* void assert (int expression); If NDEBUG is defined, do nothing. If not, and EXPRESSION is zero, print an error message and abort. #ifdef NDEBUG (__ASSERT_VOID_CAST (0))
*/
If NDEBUG is defined, do nothing. If not, and ERRNUM is not zero, print an error message with the error text for ERRNUM and abort. (This is a GNU extension.) */ # ifdef __USE_GNU # define assert_perror(errnum) # endif #else /* Not NDEBUG. */
(__ASSERT_VOID_CAST (0))
#ifndef _ASSERT_H_DECLS #define _ASSERT_H_DECLS __BEGIN_DECLS /* This prints an "Assertion failed" message and aborts. */ extern void __assert_fail (__const char *__assertion, __const char *__file, unsigned int __line, __const char *__function) __THROW __attribute__ ((__noreturn__));
DCT
UFMS
285
/* Likewise, but prints the error text for ERRNUM. */ extern void __assert_perror_fail (int __errnum, __const char *__file, unsigned int __line, __const char *__function) __THROW __attribute__ ((__noreturn__));
/* The following is not at all used here but needed for standard compliance. */ extern void __assert (const char *__assertion, const char *__file, int __line) __THROW __attribute__ ((__noreturn__));
__END_DECLS #endif /* Not _ASSERT_H_DECLS */ # define assert(expr) \ ((expr) \ ? __ASSERT_VOID_CAST (0) \ : __assert_fail (__STRING(expr), __FILE__, __LINE__, __ASSERT_FUNCTION)) # ifdef __USE_GNU # define assert_perror(errnum) (!(errnum) \ ? __ASSERT_VOID_CAST (0) \ : __assert_perror_fail ((errnum), __FILE__, __LINE__, __ASSERT_FUNCTION)) # endif
/* Version 2.4 and later of GCC define a magical variable __PRETTY_FUNCTION__ which contains the name of the function currently being defined. This is broken in G++ before version 2.6. C9x has a similar variable called __func__, but prefer the GCC one since it demangles C++ function names. */ # if defined __cplusplus ? __GNUC_PREREQ (2, 6) : __GNUC_PREREQ (2, 4) # define __ASSERT_FUNCTION __PRETTY_FUNCTION__ # else # if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L # define __ASSERT_FUNCTION __func__ # else # define __ASSERT_FUNCTION ((__const char *) 0) # endif # endif #endif /* NDEBUG. */
DCT
UFMS
DA BIBLIOTECA PADRO
286
No exemplo de trecho de cdigo a seguir, a macro assert monitora o valor do ndice i do vetor v . Se esse valor no est no intervalo especicado, o programa terminado.
DCT
UFMS
DA BIBLIOTECA PADRO
287
int isgraph(int c) Devolve verdadeiro se o contedo do parmetro c um caractere imprimvel, exceto o espao em branco, isto , caracteres 33 a 126); int islower(int c) Devolve verdadeiro se o contedo do parmetro c uma letra minscula (de a a z); int isprint(int c) Devolve verdadeiro se o contedo do parmetro c um caractere imprimvel, isto , caracteres de 32 a 126; int ispunct(int c) Devolve verdadeiro se o contedo do parmetro c um caractere de pontuao; int isspace(int c) Devolve verdadeiro se o contedo do parmetro c um branco (whitespace); int isupper(int c) Devolve verdadeiro se o contedo do parmetro c uma letra maiscula (de A a Z); int isxdigit(int c)
9, ou
Funes to... Uma funo em ctype.h com identicador da forma to... faz a converso de um caractere. Se o caractere atende a uma condio, ento convertido. Caso contrrio, o caractere devolvido sem modicaes. Listamos abaixo as interfaces das funes da forma to... em ctype.h, acompanhadas de explicaes: int tolower(int c) Se o parmetro c uma letra maiscula (de A a Z), ento o caractere convertido para uma letra minscula e devolvido; int toupper(int c) Se o parmetro c uma letra minscula (de a a z), ento o caractere convertido para uma letra maiscula e devolvido.
45.3.3 Erros
A biblioteca de monitoramento de erros acessada por meio do arquivo-cabealho errno.h. Algumas funes da biblioteca padro da linguagem C indicam ocorrncia de uma falha atravs do armazenamento de um cdigo de erro, um nmero inteiro positivo, em errno , uma varivel do tipo int declarada em errno.h. O trecho de cdigo a seguir mostra o uso da varivel errno :
DCT UFMS
DA BIBLIOTECA PADRO
288
Nesse trecho de cdigo, observe que inicializamos a varivel errno declarada na biblioteca errno.h. Se o valor dessa varivel diferente de zero aps a execuo da funo sqrt , isso signica que um erro ocorreu em sua execuo, como por exemplo, quando o valor de x negativo. O valor armazenado na varivel errno , em geral, EDOM ou ERANGE , ambas macros denidas em errno.h. Essas macros representam os dois tipos de erros que podem ocorrer quando uma funo matemtica chamada: erro de domnio ou erro de intervalo, respectivamente. No exemplo acima, se x tem valor negativo, ento o valor de EDOM armazenado na varivel errno . Por outro lado, se o valor devolvido por uma funo muito grande, ultrapassando a capacidade de representao do tipo do valor a ser devolvido pela funo, ento o valor ERANGE armazenado em errno . Por exemplo, se 1000 argumento da funo exp, ento isso provoca um erro de intervalo j que e1000 muito grande para ser representado por um double .
45.3.5 Localizao
o arquivo-cabealho que contm as informaes da biblioteca de funes que ajudam um programa a adaptar seu comportamento a um pas ou a uma regio geogrca. O comportamento especco de um local inclui a forma como os nmeros so impressos, o formato dos valores monetrios, o conjunto de caracteres e a aparncia da data e da hora.
locale.h
Com a mudana de um local, um programa pode adaptar seu comportamento para uma rea diferente do mundo. Mas essa mudana pode afetar muitas partes da biblioteca, algumas das quais podemos preferir no alterar.
DCT
UFMS
DA BIBLIOTECA PADRO
289
45.3.6 Matemtica
A biblioteca matemtica da linguagem C pode ser acessada atravs do arquivo-cabealho que contm os prottipos de diversas funes matemticas teis, agrupadas em funes trigonomtricas, funes logartmicas, de exponenciao e de potenciao, alm de outras funes.
math.h,
Quando inclumos o arquivo-cabealho math.h em um programa e usamos suas funes, importante destacar que h necessidade de adicionar a diretiva de compilao -lm no processo de compilao deste programa, na chamada do gcc . Caso contrrio, um erro de compilao ser emitido pelo compilador da linguagem C. Constantes Uma nica macro denida neste arquivo-cabealho: HUGE_VAL . Essa macro indica que o valor devolvido por uma funo muito grande para ser representado como um nmero de preciso dupla. HUGE_VAL do tipo double e pode ser entendido como innito. Erros Todas as funes da biblioteca matemtica da linguagem C manipulam erros de forma similar. No caso em que o argumento passado funo excede o intervalo permitido, ento a varivel errno recebe o valor EDOM . O valor devolvido pela funo depende da mquina. No caso do valor devolvido ser muito grande para ser representado como um nmero de preciso dupla, ento a funo devolve a macro HUGE_VAL e atualiza a varivel errno para ERANGE . Se o valor muito pequeno para ser representado como um nmero de preciso dupla, ento a funo devolve zero. Nesse caso, se a varivel errno recebe ERANGE ou no uma deciso dependente da mquina. Funes trigonomtricas Listamos abaixo as interfaces das funes trigonomtricas no arquivo-cabealho math.h, acompanhadas de explicaes: double cos(double x) Devolve o cosseno de um ngulo x dado em radianos. O valor devolvido est no intervalo [1, +1]; double sin(double x) Devolve o seno de um ngulo x dado em radianos. O valor devolvido est no intervalo [1, +1]; double tan(double x) Devolve a tangente de um ngulo x dado em radianos;
DCT
UFMS
DA BIBLIOTECA PADRO
290
double acos(double x) Devolve o arco-cosseno de x em radianos. O valor de x deve estar no intervalo [1, +1]. O valor devolvido est no intervalo [0, ]; double asin(double x) Devolve o arco-seno de x em radianos. O valor de x deve estar no intervalo [1, +1]. O valor devolvido est no intervalo [/2, +/2]; double atan(double x) Devolve o arco-tangente de x em radianos. [/2, +/2]; double atan2(double y, double x) Devolve o arco-tangente em radianos de y/x baseado nos sinais de ambos os valores para determinar o quadrante correto. O valor devolvido est no intervalo [/2, /2]; double cosh(double x) Devolve o cosseno hiperblico de x; double sinh(double x) Devolve o seno hiperblico de x; double tanh(double x) Devolve a tangente hiperblica de x. O valor devolvido est no intervalo [1, +1]. Funes logartmicas, de exponenciao e de potenciao Listamos abaixo as interfaces das funes logartmicas, de exponenciao e de potenciao em math.h, acompanhadas de explicaes: double exp(double x) Devolve o valor de e elevado x-sima potncia; double frexp(double x, int *expoente) O nmero com ponto utuante x subdividido em uma mantissa e um expoente. O valor devolvido pela funo a mantissa e o parmetro de entrada e sada expoente contm o expoente. Observe que expoente x = matissa 2 . A mantissa deve estar no intervalo [0.5, 1.0]. double ldexp(double x, int expoente) Devolve x multiplicado por 2 elevado potncia expoente, isto , x 2expoente . double log(double x) Devolve o logaritmo natural de x, isto , o logaritmo na base e; O valor devolvido est no intervalo
DCT
UFMS
DA BIBLIOTECA PADRO
291
double log10(double x) Devolve o logaritmo de x na base 10; double modf(double x, double *inteiro) Subdivide o nmero com ponto utuante x nas partes inteira e fracionria. O valor devolvido pela funo a parte fracionria, depois do ponto decimal, e o parmetro de entrada e sada inteiro contm a parte inteira de x; double pow(double x, double y) Devolve x elevado potncia y. O valor de x no pode ser negativo se y um valor fracionrio. x no pode ser zero se y menor ou igual a zero. double sqrt(double x) Devolve a raiz quadrada de x. O valor armazenado em x no pode ser negativo. Outras funes Listamos abaixo as interfaces de outras funes matemticas em math.h, acompanhadas de explicaes: double ceil(double x) Devolve o menor inteiro que maior ou igual a x; double floor(double x) Devolve o maior inteiro que menor ou igual a x; double fabs(double x) Devolve o valor absoluto de x. Isto , se x um valor negativo, a funo devolve o valor positivo. Caso contrrio, devolve o valor de x; double fmod(double x, double y) Devolve o resto de x dividido por y. O valor de y deve ser diferente de zero.
DCT
UFMS
DA BIBLIOTECA PADRO
292
DA BIBLIOTECA PADRO
293
formatao especicam caracteres que devem corresponder na entrada mas no armazenados nos argumentos da funo. Um branco pode corresponder a qualquer outro branco ou ao prximo caractere incompatvel. Mais informaes sobre entrada formatada, veja a aula 18. Se a execuo da funo obteve sucesso, o nmero de valores lidos, convertidos e armazenados nas variveis devolvido. Entrada e sada de caracteres Listamos abaixo as interfaces das funes de entrada e sada de caracteres em stdio.h, acompanhadas de explicaes: int getchar(void) L um caractere (um unsigned char) da entrada padro. Se a leitura tem sucesso, o caractere devolvido; int putchar(int caractere) Imprime um caractere (um unsigned char) especicado pelo argumento caractere na sada padro. Se a impresso tem sucesso, o caractere devolvido.
DCT
UFMS
DA BIBLIOTECA PADRO
294
double atof(const char *cadeia) A cadeia de caracteres cadeia convertida e devolvida como um nmero de ponto utuante do tipo double . Brancos iniciais so ignorados. O nmero pode conter um sinal opcional, uma seqncia de dgitos com um caractere de ponto decimal opcional, mais uma letra e ou E opcional seguida por um expoente opcionalmente sinalizado. A converso pra quando o primeiro caractere no reconhecvel encontrado. Se a converso foi realizada com sucesso, o nmero devolvido. Caso contrrio, zero devolvido; int atoi(const char *cadeia) A cadeia de caracteres cadeia convertida e devolvida como um nmero inteiro do tipo int . Bancos iniciais so ignorados. O nmero pode conter um sinal opcional, mais uma seqncia de dgitos. A converso pra quando o primeiro caractere no reconhecvel encontrado. Se a converso foi realizada com sucesso, o nmero devolvido. Caso contrrio, zero devolvido; long int atol(const char *cadeia) A cadeia de caracteres cadeia convertida e devolvida como um nmero inteiro do tipo long int . Brancos iniciais so ignorados. O nmero pode conter um sinal opcional, mais uma seqncia de dgitos. A converso pra quando o primeiro caractere no reconhecvel encontrado. Se a converso foi realizada com sucesso, o nmero devolvido. Caso contrrio, zero devolvido. Funes de ambiente Listamos abaixo as interfaces das funes de ambiente em stdlib.h, acompanhadas de explicaes: void abort(void) Finaliza o programa anormalmente, sinalizando o trmino sem sucesso para o ambiente; void exit(int estado) Finaliza o programa normalmente, sinalizando para o ambiente o valor armazenado em estado , que pode ser EXIT_FAILURE ou EXIT_SUCCESS . Funes matemticas Listamos abaixo as interfaces das funes matemticas em stdlib.h, acompanhadas de explicaes: int abs(int x) Devolve o valor absoluto de x ;
DCT
UFMS
DA BIBLIOTECA PADRO
295
long int labs(long int x) Devolve o valor absoluto de x ; int rand(void) Devolve um nmero pseudo-aleatrio no intervalo [0, RAND_MAX ]; void srand(unsigned int semente) Inicializa o gerador de nmero pseudo-aleatrios usado pela funo rand . Inicializar srand com o mesmo valor de semente faz com que a funo rand devolva a mesma seqncia de nmeros pseudo-aleatrios. Se srand no for executada, rand age como se srand(1) tivesse sido executada.
DCT
UFMS
DA BIBLIOTECA PADRO
296
Compara no mximo os primeiros n caracteres de cadeia1 e cadeia2 . Finaliza a comparao aps encontrar um caractere nulo. Devolve zero se os n primeiros caracteres, ou os caracteres at alcanar o caractere nulo, de cadeia1 e cadeia2 so iguais. Devolve um valor menor que zero ou maior que zero se cadeia1 menor que ou maior que cadeia2 , respectivamente; char *strcpy(char *cadeia1, const char *cadeia2) Copia a cadeia de caracteres cadeia2 na cadeia de caracteres cadeia1 . Copia at o caractere nulo de cadeia2 , inclusive. Devolve um apontador para cadeia1 ; char *strncpy(char *cadeia1, const char *cadeia2, unsigned int n) Copia at n caracteres de cadeia2 em cadeia1 . A cpia termina quando n caracteres so copiados ou quando o caractere nulo em cadeia2 alcanado. Se o caractere nulo alcanado, os caracteres nulos so continuamente copiados para cadeia1 at que n caracteres tenham sido copiados; size_t strlen(const char *cadeia) Computa o comprimento da cadeia , no incluindo o caractere nulo. Devolve o nmero de caracteres da cadeia ; char *strpbrk(const char *cadeia1, const char *cadeia2) Procura o primeiro caractere na cadeia1 que corresponde a algum caractere especicado na cadeia2 . Se encontrado, um apontador para a localizao deste caractere devolvido. Caso contrrio, o apontador para nulo devolvido; char *strchr(const char *cadeia, int c) Busca pela primeira ocorrncia do caractere c na cadeia de caracteres cadeia . Devolve um apontador para a posio que contm o primeiro caractere na cadeia que corresponde a c ou nulo se c no ocorre em cadeia ; char *strrchr(const char *cadeia, int c) Busca pela ltima ocorrncia do caractere c na cadeia de caracteres cadeia . Devolve um apontador para a posio que contm o ltimo caracter na cadeia que corresponde c ou nulo se c no ocorre em cadeia ; size_t strspn(const char *cadeia1, const char *cadeia2) Encontra a primeira seqncia de caracteres na cadeia1 que contm qualquer caracter da cadeia2 . Devolve o comprimento desta primeira seqncia de caracteres que corresponde cadeia2 ; size_t strcspn(const char *cadeia1, const char *cadeia2) Encontra a primeira seqncia de caracteres na cadeia1 que no contm qualquer caracter da cadeia2 . Devolve o comprimento desta primeira seqncia de caracteres que no corresponde cadeia2 ; char *strstr(const char *cadeia1, const char *cadeia2) Encontra a primeira ocorrncia da cadeia2 na cadeia1 . Devolve um apontador para a localizao da primeira ocorrncia da cadeia2 na cadeia1 . Se no h correspondncia, o apontador nulo devolvido. Se cadeia2 contm uma cadeia de caracteres de comprimento zero, ento cadeia1 devolvida.
DCT UFMS
DA BIBLIOTECA PADRO
297
Exerccios
45.1 (a) Escreva uma funo com a seguinte interface
double cosseno(double x, double epsilon)
que receba um nmero real x e um nmero positivo real > 0, e calcule uma aproximao para cos x, onde x dado em radianos, atravs da seguinte srie: cos x = 1 x2k x2 x4 x6 + + . . . + (1)k + ... 2! 4! 6! (2k)!
x2k (2k )! ,
at que Tk < .
que receba um nmero real x e um nmero real > 0, e calcule uma aproximao para sen x, onde x dado em radianos, atravs da seguinte srie: sen x = x x3 x5 x2k+1 + . . . + (1)k + ... 1! 3! 5! (2k + 1)!
x2k+1 (2k +1)! ,
at que Tk < .
(c) Escreva um programa que receba um nmero real x representando um ngulo em radianos e um nmero real > 0 representando uma preciso, e calcule o valor de tan x de duas formas: usando as funes dos itens acima e usando a funo tan da biblioteca math . Compare os resultados. 45.2 (a) Escreva uma funo com a seguinte interface:
int ocorre(char palavra[], char frase[], int pos)
que receba duas cadeias de caracteres palavra e frase e um inteiro pos e verique se a cadeia de caracteres palavra ocorre na posio pos da cadeia de caracteres frase . Em caso positivo, a funo deve devolver 1. Em caso negativo, deve devolver zero.
DCT UFMS
DA BIBLIOTECA PADRO
298
(b) Escreva um programa que receba duas cadeias de caracteres padrao e texto , com m e n caracteres respectivamente e m n, e imprima o nmero de vezes que padrao ocorre em texto . Use todas as funes da biblioteca string que puder.
DCT
UFMS
A ULA 46
P R - PROCESSADOR
O pr-processador um mdulo da linguagem C que edita um programa antes de sua compilao. uma ferramenta poderosa e que diferencia a linguagem C das demais linguagens de programao do alto nvel. As diretivas #include e #define que usamos em aulas anteriores so manipuladas pelo pr-processador, assim como outras que veremos nesta aula. Apesar de poderoso, o mal uso do pr-processador pode produzir programas praticamente ininteligveis e/ou com erros muito difceis de encontrar.
46.1 Funcionamento
As diretivas de pr-processamento controlam o comportamento do pr-processador. Uma diretiva do pr-processador um comando que inicia com o caractere # . At o momento, vimos duas das diretivas do pr-processador da linguagem C: #include e #define . Revisaremos essas diretivas adiante. A gura 46.1 ilustra o papel do pr-processador durante o processo de compilao.
programa em C
pr-processador
programa em C modicado
compilador
cdigo objeto
46.2 D IRETIVAS
DE PR - PROCESSAMENTO
300
A entrada para o pr-processador um programa escrito na linguagem C, possivelmente contendo diretivas. O pr-processador executa ento estas diretivas, eliminando-as durante este processo. A sada produzida pelo pr-processador um outro programa na linguagem C, que representa uma verso editada do programa original, sem diretivas. A sada do prprocessador a entrada para o compilador, que verica erros no programa e realiza sua traduo para o cdigo objeto, que contm apenas instrues diretas para a mquina.
define
MAX
100
Diretivas sempre terminam no primeiro caractere de mudana de linha, a menos que a continuao seja explcita. Para continuar uma diretiva em uma prxima linha, devemos nalizar a linha atual com o caractere \ . Por exemplo, a diretiva abaixo dene uma macro que representa a capacidade de um disco medida em bytes:
\ \ \
DCT
UFMS
46.3 D EFINIES
DE MACROS
301
Diretivas podem aparecer em qualquer lugar em um programa. Apesar de usualmente colocarmos as diretivas #include e #define no comeo de um arquivo, outras diretivas so mais provveis de ocorrer em outros pontos do programa. Comentrios podem ocorre na mesma linha de uma diretiva. Em geral, colocamos um comentrio no nal da denio de uma macro para explicar seu signicado. Por exemplo:
onde lista-de-troca uma seqncia qualquer de itens. Essa lista pode incluir identicadores, palavras reservadas, constantes numricas, constantes de caracteres, literais, operadores e pontuao. Quando encontra uma denio de uma macro, o pr-processador toma nota que o identificador representa a lista-de-troca . Sempre que identificador ocorre posteriormente no arquivo, o pr-processador o substitui pela lista-de-troca . Um erro freqente na denio de macros a incluso de smbolos extras, que conseqentemente faro parte da lista de troca. Abaixo so mostrados dois exemplos de erros como esse.
#define TAM = 100 #define N 10; ... int u[TAM]; double v[N];
A traduo do trecho de cdigo acima realizada pelo pr-processador gera o seguinte trecho de cdigo modicado, correspondente s duas ltima linhas:
DCT
UFMS
46.4 I NCLUSO
DE ARQUIVOS - CABEALHOS
302
certo que o compilador acusar erros nessa duas linhas do programa. Podemos usar macros para dar nomes a valores numricos, caracteres e literais, como ilustrado abaixo:
Usar a diretiva #define para criar nomes para constantes tem diversas vantagens, como tornar os programas mais fceis de ler e de modicar, ajudar a evitar inconsistncias e erros tipogrcos, permitir pequenas mudanas na sintaxe da linguagem, renomear tipos e controlar a compilao condicional.
#include <arquivo.h>
A segunda forma usada para todos os outros arquivos-cabealhos, incluindo aqueles que so escritos por programadores(as):
#include "arquivo.h"
DCT
UFMS
46.4 I NCLUSO
DE ARQUIVOS - CABEALHOS
303
#include <stdio.h> #define #define #define #define #define #define #define #define #define #define #define PAR IMPAR Programa inicio fim escreva leia se senao enquanto devolva 0 1 main { } printf scanf if else while return
int verificaParidade(int numero) inicio int paridade; se (numero % 2 == 0) paridade = PAR; senao paridade = IMPAR; devolva paridade; fim int Programa(void) inicio int i, n, numero, somapar, somaimpar; escreva("Informe a quantidade de elementos: "); leia("%d", &n); somapar = 0; somaimpar = 0; i = 1; enquanto (i <= n) inicio escreva("Informe um nmero: "); leia("%d", &numero); se (verificaParidade(numero) == PAR) somapar = somapar + numero; senao somaimpar = somaimpar + numero; i++; fim escreva("\nSoma dos nmeros pares: %d\n", somapar); escreva("Soma dos nmeros mpares: %d\n", somaimpar); devolva 0; fim
DCT
UFMS
46.5 C OMPILAO
CONDICIONAL
304
A diferena entre as duas formas se d pela maneira como o compilador busca o arquivocabealho. Na primeira, o compilador busca o arquivo-cabealho no(s) diretrio(s) em que os arquivos-cabealhos do sistema se encontram. Nos sistemas baseados no UNIX, como o LINUX, os arquivos-cabealhos so mantidos usualmente no diretrio /usr/include . Na segunda forma, a busca realizada no diretrio corrente e, em seguida, no(s) diretrio(s) em que os arquivos-cabealhos do sistema se encontram. Arquivos-cabealhos auxiliam no compartilhamento de denies de macros, de tipos e de prottipos de funes por dois ou mais arquivos de cdigo fonte. Na aula 47, quando apresentamos formas de tratar programas muito grandes divididos em arquivos diferentes, mencionamos novamente a diretiva #include , destacando formas de seu uso no compartilhamento de denies de macros, de tipos e de prottipos de funes.
#if expresso-constante
#endif
Quando o pr-processador encontra a diretiva #if , ele avalia a expresso-constante . Se o resultado da avaliao igual a zero, as linhas entre #if e #endif sero removidas do programa durante o pr-processamento. Caso contrrio, as linhas entre #if e #endif sero mantidas e processadas pelo compilador. O seguinte trecho de cdigo exemplica o uso dessas diretivas e permite vericar seu uso no auxlio do processo de depurao.
#define DEBUG 1 ... #if DEBUG printf("Valor de i = %d\n", i); printf("Valor de j = %d\n", j); #endif
DCT
UFMS
46.5 C OMPILAO
CONDICIONAL
305
Durante o pr-processamento, a diretiva #if verica o valor de DEBUG . Como seu valor diferente de zero, o pr-processador mantm as duas chamadas funo printf no programa, mas as linhas contendo as diretivas #if e #endif sero eliminadas aps o prprocessamento. Se modicamos o valor de DEBUG para zero e recompilamos o programa, ento o pr-processador remove todas as 4 linhas do programa. importante notar que a diretiva #if trata identicadores no denidos como macros que tm o valor zero. Assim, se esquecemos de denir DEBUG , o teste
#if DEBUG
#if !DEBUG
produzir o resultado verdadeiro, nesse caso. Nesse sentido, h duas outras diretivas para vericar se um identicador denido ou no como uma macro naquele ponto do programa: #ifdef e #ifndef . O formato geral dessas diretivas
#ifdef identificador Linhas a serem includas se o identificador est definido como uma macro #endif
#ifndef identificador Linhas a serem includas se o identificador no est definido como uma macro #endif
As diretivas #elif e #else podem ser usadas em conjunto com as diretivas #if , #ifdef e #ifndef quando aninhamento de blocos so necessrios. O formato geral dessas diretivas apresentado abaixo:
#elif expresso-constante
DCT
UFMS
46.6 O UTRAS e
DIRETIVAS
306
#else
#if expresso1 Linhas a serem includas se expresso1 diferente de zero #elif expresso2 Linhas a serem includas se expresso2 diferente de zero #else Linhas a serem includas em caso contrrio #endif
A compilao condicional, alm de auxiliar na depurao de programas, tambm tem muitos outros usos como na construo de programas que podem ser transportados de uma mquina para outra ou de um sistema operacional para outro, na construo de programas que podem ser compilados por compiladores diferentes, no suporte denies padronizadas de macros, entre outros.
#error mensagem
onde mensagem uma seqncia qualquer de caracteres. Um exemplo de uso dessa diretiva apresentado no trecho de cdigo a seguir:
DCT
UFMS
46.6 O UTRAS
DIRETIVAS
307
Se o pr-processador encontra uma diretiva #error , isso sinal de que uma falha grave ocorreu no programa e alguns compiladores terminam imediatamente a compilao. A diretiva #line usada para alterar a forma como as linhas do programa so numeradas. O formato geral dessa diretiva apresentado abaixo:
#line n
#line n "arquivo"
No primeiro formato, as linhas do programa so numeradas a partir do nmero n . No segundo, as linhas do programa no arquivo so numeradas a partir do nmero n . Muitos compiladores usam essa informao quando precisam gerar mensagens de erro. Por m, a diretiva #pragma permite que uma diretiva seja criada pelo(a) programador(a). O formato geral dessa diretiva dado abaixo:
#pragma diretiva
DCT
UFMS
A ULA 47
P ROGRAMAS
EXTENSOS
Nesta aula aprenderemos como dividir e distribuir nossas funes em vrios arquivos. Esta possibilidade uma caracterstica importante da linguagem C, permitindo que o(a) programador(a) possa ter sua prpria biblioteca de funes e possa us-la em conjunto com um ou mais programas. Os programas que escrevemos at o momento so bem simples e, por isso mesmo pequenos, com poucas linhas de cdigo. No entanto, programas pequenos so uma exceo. medida que os problemas tm maior complexidade, os programas para solucion-los tm, em geral, proporcionalmente mais linhas de cdigo. Por exemplo, a verso 2.6.25 do ncleo do sistema operacional LINUX, de abril de 2008, tem mais de nove milhes de linhas de cdigo na linguagem C e seria impraticvel mant-las todas no mesmo arquivo1 . Nesta aula veremos que um programa na linguagem C consiste de vrios arquivos-fontes e tambm de alguns arquivos-cabealhos. aprenderemos a dividir nossos programas em mltiplos arquivos.
47.1 Arquivos-fontes
At a ltima aula, sempre consideramos que um programa na linguagem C consiste de um nico arquivo. Na verdade, um programa pode ser dividido em qualquer nmero de arquivosfontes que, por conveno, tm a extenso .c . Cada arquivo-fonte contm uma parte do programa, em geral denies de funes e variveis. Um dos arquivos-fontes de um programa deve necessariamente conter uma funo main , que o ponto de incio do programa. Quando dividimos um programa em arquivos, faz sentido colocar funes relacionadas e variveis em um mesmo arquivo-fonte. A diviso de um programa em arquivos-fontes mltiplos tem vantagens signicativas: agrupar funes relacionadas e variveis em um nico arquivo ajuda a deixar clara a estrutura do programa; cada arquivo-fonte pode ser compilado separadamente, com uma grande economia de tempo se o programa grande modicado muitas vezes; funes so mais facilmente re-usadas em outros programas quando agrupadas em arquivos-fontes separados.
1 O sistema operacional LINUX, por ser livre e de cdigo aberto, permite que voc possa consultar seu cdigo fonte, alm de modic-lo a seu gosto.
308
309
47.2 Arquivos-cabealhos
Nas aulas 45 e 46 abordamos com algum detalhe os arquivos-cabealhos. Quando dividimos um programa em vrios arquivos-fonte, como uma funo denida em um arquivo pode chamar uma outra funo denida em outro arquivo? Como dois arquivos podem compartilhar a denio de uma mesma macro ou a denio de um tipo? A diretiva #include nos ajuda a responder a essas perguntas, permitindo que essas informaes possam ser compartilhadas entre arquivos-fontes. Muitos programas grandes contm denies de macros e denies de tipos que necessitam ser compartilhadas por vrios arquivos-fontes. Essas denies devem ser mantidas em arquivos-cabealhos. Por exemplo, suponha que estamos escrevendo um programa que usa macros com nomes
LOGIC , VERDADEIRO e FALSO . Ao invs de repetir essas macros em cada arquivo-fonte do
programa que necessita delas, faz mais sentido colocar as denies em um arquivo-cabealho com um nome como logico.h tendo as seguintes linhas:
Qualquer arquivo-fonte que necessite dessas denies deve conter simplesmente a linha a seguir:
#include "logico.h"
Denies de tipos tambm so comuns em arquivos-cabealhos. Por exemplo, ao invs de denir a macro LOGIC acima, podemos usar typedef para criar um tipo logic . Assim, o arquivo logico.h ter as seguintes linhas:
A gura 47.1 mostra um exemplo de dois arquivos-fontes que incluem o arquivo-cabealho logico.h . Colocar denies de macros e tipos em um arquivo-cabealho tem algumas vantagens. Primeiro, economizamos tempo por no ter de copiar as denies nos arquivos-fontes onde so necessrias. Segundo, o programa torna-se muito mais fcil de modicar, j que a modicao
DCT UFMS
310
#include "logic.h"
#include "logic.h"
Figura 47.1: Incluso de um arquivo-cabealho em dois arquivos-fontes. da denio de uma macro ou de um tipo necessita ser feita em um nico arquivo-cabealho. E terceiro, no temos de nos preocupar com inconsistncias em conseqncia de arquivos-fontes contendo denies diferentes da mesma macro ou tipo. Suponha agora que um arquivo-fonte contm uma chamada a uma funo soma que est denida em um outro arquivo-fonte, com nome calculos.c . Chamar a funo soma sem sua declarao pode ocasionar erros de execuo. Quando chamamos uma funo que est denida em outro arquivo, sempre importante ter certeza que o compilador viu sua declarao, isto , seu prottipo, antes dessa chamada. Nosso primeiro impulso declarar a funo soma no arquivo-fonte onde ela foi chamada. Isso resolve o problema, mas pode criar imensos problemas de gerenciamento. Suponha que a funo chamada em cinqenta arquivos-fontes diferentes. Como podemos assegurar que os prottipos das funes soma so idnticos em todos esses arquivos? Como podemos garantir que todos esses prottipos correspondem denio de soma em calculos.c ? Se soma deve ser modicada posteriormente, como podemos encontrar todos os arquivos-fontes onde ela usada? A soluo evidente: coloque o prottipo da funo soma em um arquivocabealho e inclua ento esse arquivo-cabealho em todos os lugares onde soma chamada. Como soma denida em calculos.c , um nome natural para esse arquivo-cabealho calculos.h . Alm de incluir calculos.h nos arquivos-fontes onde soma chamada, precisamos inclu-lo em calculos.c tambm, permitindo que o compilador verique que o prottipo de soma em calculos.h corresponde sua denio em calculos.c . regra sempre incluir o arquivo-cabealho que declara uma funo em um arquivo-fonte que contm a denio dessa funo. No faz-lo pode ocasionar erros difceis de encontrar. Se calculos.c contm outras funes, muitas delas devem ser declaradas no mesmo arquivo-cabealho onde foi declarada a funo soma . Mesmo porque, as outras funes em
DCT UFMS
311
calculos.c so de alguma forma relacionadas com soma . Ou seja, qualquer arquivo que contenha uma chamada soma provavelmente necessita de alguma das outras funes em calculos.c . Funes cuja inteno seja us-las apenas como suporte dentro de calculos.c no devem ser declaradas em um arquivo-cabealho.
Para ilustrar o uso de prottipos de funes em arquivos-cabealhos, vamos supor que queremos manter diversas denies de funes relacionadas a clculos geomtricos em um arquivo-fonte geometricas.c . As funes so as seguintes:
double perimetroQuadrado(double lado) { return 4 * lado; } double perimetroTriangulo(double lado1, double lado2, double lado3) { return lado1 + lado2 + lado3; } double perimetroCirculo(double raio) { return 2 * PI * raio; } double areaQuadrado(double lado) { return lado * lado; } double areaTriangulo(double base, double altura) { return base * altura / 2; } double areaCirculo(double raio) { return PI * raio * raio; } double volumeCubo(double lado) { return lado * lado * lado; } double volumeTetraedro(double lado, double altura) { return (double)1/3 * areaTriangulo(lado, lado * sqrt(altura) / 2) * altura; } double volumeEsfera(double raio) { return (double)4/3 * PI * raio * raio; }
Os prottipos dessas funes, alm de uma denio de uma macro, sero mantidos em um arquivo-cabealho com nome geometricas.h :
DCT UFMS
312
perimetroQuadrado(double lado); perimetroCirculo(double raio); perimetroTriangulo(double lado1, double lado2, double lado3); areaQuadrado(double lado); areaCirculo(double raio); areaTriangulo(double base, double altura); volumeCubo(double lado); volumeEsfera(double raio); volumeTetraedro(double lado, double altura);
Alm desses prottipos, a macro PI tambm deve ser denida neste arquivo. Ento, um arquivo-fonte calc.c que calcula medidas de guras geomtricas e que contm a funo main pode ser construdo. A gura 47.2 ilustra essa diviso.
geometricas.h
#define PI 3.141592 double perimetroQuadrado(double lado); double perimetroCirculo(double raio);
#include "geometricas.h"
#include "geometricas.h"
int main(void) {
calc.c
geometricas.c
DCT
UFMS
47.3 D IVISO
DE PROGRAMAS EM ARQUIVOS
313
criamos um arquivo-cabealho com o mesmo nome do arquivo-fonte, mas com extenso .h . O arquivo-cabealho geometricas.h da seo anterior um exemplo. Nesse arquivocabealho, colocamos os prottipos das funes includas no arquivo-fonte, lembrando que as funes que so projetadas somente para dar suporte s funes do arquivo-fonte no devem ter seus prottipos descritos no arquivo-cabealho. Ento, devemos incluir o arquivocabealho em cada arquivo-fonte que necessite chamar uma funo denida no arquivo-fonte. Alm disso, inclumos o arquivo-cabealho no prprio arquivo-fonte para que o compilador possa vericar que os prottipos das funes no arquivo-cabealho so consistentes com as denies do arquivo-fonte. Esse o caso do exemplo da seo anterior, com arquivo-fonte geometricas.c e arquivo-cabealho geometricas.h . Veja novamente a gura 47.2. A funo principal main deve ser includa em um arquivo-fonte que tem um nome representando o nome do programa. Por exemplo, se queremos que o programa seja conhecido como calc ento a funo main deve estar em um arquivo-fonte com nome calc.c . possvel que existam outras funes no mesmo arquivo-fonte onde main se encontra, funes essas que no so chamadas em outros arquivos-fontes do programa. Para gerar um arquivo-executvel de um programa dividido em mltiplos arquivos-fontes, os mesmos passos bsicos que usamos para um programa em um nico arquivo-fonte so necessrios: compilao: cada arquivo-fonte do programa deve ser compilado separadamente; para cada arquivo-fonte, o compilador gera um arquivo contendo cdigo objeto, que tm extenso .o ; ligao: o ligador combina os arquivos-objetos criados na fase compilao, juntamente com cdigo das funes da biblioteca, para produzir um arquivo-executvel. Muitos compiladores nos permitem construir um programa em um nico passo. Com o compilador GCC, usamos o seguinte comando para construir o programa calc da seo anterior:
Os dois arquivos-fontes so primeiro compilados em arquivos-objetos. Esse arquivosobjetos so automaticamente passados para o ligador que os combina em um nico arquivo. A opo -o especica que queremos que nosso arquivo-executvel tenha o nome calc .
DCT UFMS
47.3 D IVISO
DE PROGRAMAS EM ARQUIVOS
314
Digitar os nomes de todos os arquivos-fontes na linha de comando de uma janela de um terminal logo torna-se uma tarefa tediosa. Alm disso, podemos desperdiar uma quantidade de tempo quando reconstrumos um programa se sempre recompilamos todos os arquivosfontes, no apenas aqueles que so afetados pelas nossas modicaes mais recentes. Para facilitar a construo de grandes programas, o conceito de makeles foi proposto nos primrdios da criao do sistema operacional UNIX. Um makele um arquivo que contm informao necessria para construir um programa. Um makele no apenas lista os arquivos que fazem parte do programa, mas tambm descreve as dependncias entre os arquivos. Por exemplo, da seo anterior, como calc.c inclui o arquivo geometricas.h , dizemos que calc.c depende de geometricas.h , j que uma mudana em geometricas.h far com que seja necessria a recompilao de calc.c . Abaixo, listamos um makele para o programa calc .
calc: calc.o geometricas.o gcc -o calc calc.o geometricas.o -lm calc.o: calc.c geometricas.h gcc -c calc.c -lm geometricas.o: geometricas.c geometricas.h gcc -c geometricas.c -lm
No arquivo acima, existem 3 grupos de linhas. Cada grupo conhecido como um regra. A primeira linha em cada regra fornece um arquivo-alvo, seguido pelos arquivos dos quais ele depende. A segunda linha um comando a ser executado se o alvo deve ser reconstrudo devido a uma alterao em um de seus arquivos de dependncia. Na primeira regra, calc o alvo:
A primeira linha dessa regra estabelece que calc depende dos arquivos calc.o e geometricas.o . Se qualquer um desses dois arquivos foi modicado desde da ltima construo do programa, ento calc precisa ser reconstrudo. O comando na prxima linha indica como a reconstruo deve ser feita, usando o GCC para ligar os dois arquivos-objetos. Na segunda regra, calc.o o alvo:
DCT
UFMS
47.3 D IVISO
DE PROGRAMAS EM ARQUIVOS
315
A primeira linha indica que calc.o necessita ser reconstrudo se ocorrer uma alterao em calc.c ou geometricas.h . A prxima linha mostra como atualizar calc.o atravs da recompilao de calc.c . A opo -c informa o compilador para compilar calc.c em um arquivo-objeto, sem lig-lo. Tendo criado um makele para um programa, podemos usar o utilitrio make para construir ou reconstruir o programa. vericando a data e a hora associada com cada arquivo do programa, make determina quais arquivos esto desatualizados. Ento, ele invoca os comandos necessrios para reconstruir o programa. Algumas dicas para criar makeles seguem abaixo: cada comando em um makele deve ser precedido por um caractere de tabulao horizontal T A B ; um makele armazenado em um arquivo com nome Makefile ; quando o utilitrio make usado, ele automaticamente verica o contedo do diretrio atual buscando por esse arquivo; use
make alvo
onde alvo um dos alvos listados no makele; se nenhum alvo especicado, make construir o alvo da primeira regra. O utilitrio make complicado o suciente para existirem dezenas de livros e manuais que nos ensinam a us-lo. Com as informaes desta aula, temos as informaes bsicas necessrias para us-lo na construo de programas extensos divididos em diversos arquivos. Mais informaes sobre o utilitrio make devem ser buscadas no manual do GNU/Make.
Exerccios
47.1 (a) Escreva uma funo com a seguinte interface:
void preencheAleatorio(int v[], int n)
que receba um vetor v de nmeros inteiros e um nmero inteiro n e gere n nmeros inteiros aleatrios armazenando-os em v . Use a funo rand da biblioteca stdlib . (b) Crie um arquivo-fonte com todas as funes de ordenao que vimos nas aulas 42, 43 e 44. Crie tambm um arquivo-cabealho correspondente. (c) Escreva um programa que receba um inteiro n, com 1 n 10000, gere um seqncia de n nmeros aleatrios e execute os mtodos de ordenao que conhecemos sobre esse vetor, medindo seu tempo de execuo. Use as funes clock e difftime da biblioteca time . (d) Crie um makele para compilar e ligar seu programa.
DCT
UFMS
A ULA 48
O PERAES
SOBRE BITS
A linguagem C possui muitas caractersticas de uma linguagem de programao de alto nvel e isso signica que podemos escrever programas que so independentes das mquinas que iro execut-los. A maioria das aplicaes possui naturalmente essa propriedade, isto , podemos propor solues computacionais programas escritos na linguagem C que no tm de se adaptar s mquinas em que sero executados. Por outro lado, alguns programas mais especcos, como compiladores, sistemas operacionais, cifradores, processadores grcos e programas cujo tempo de execuo e/ou uso do espao de armazenamento so crticos, precisam executar operaes no nvel dos bits. Outra caracterstica importante da linguagem C, que a diferencia de outras linguagens de programao alto nvel, que ela permite o uso de operaes sobre bits especcos e sobre trechos de bits, e ainda permite o uso de estruturas de armazenamento para auxiliar nas operaes de baixo nvel. Nesta aula, tratamos com algum detalhe de cada um desses aspectos da linguagem C.
Signicado complemento
E
O operador de complemento ~ o nico operador unrio dessa tabela, os restantes todos so binrios. Os operadores ~, &, ^ e | executam operaes booleanas sobre os bits de seus operandos. O operador ~ gera o complemento de seu operando, isto , troca os bits 0 por 1 e os bits 1 por 0. O operador & executa um E booleano bit a bit nos seus operandos. Os operadores ^ e | executam um OU booleano bit a bit nos seus operandos, sendo que o operador ^ produz 0 caso os dois bits correspondentes dos operandos sejam 1 e, nesse mesmo caso, o operador | produz 316
48.1 O PERADORES
BIT A BIT
317
1. Os operadores de deslocamento << e >> so operadores binrios que deslocam os bits de seu operando esquerda um nmero de vezes representado pelo operando direita. Por exemplo, i << j produz como resultado o valor de i deslocado de j posies esquerda. Para cada bit que deslocado esquerda para fora do nmero, um bit 0 adicionado direita desse nmero. O mesmo vale inversamente para o operador de deslocamento direita >>. O programa 48.1 mostra um exemplo simples do uso de todos os operadores sobre bits. Programa 48.1: Uso de operadores sobre bits.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
#include <stdio.h> void imprime(unsigned short int n, char msg[]) { char binario[17] = {0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,\0}; unsigned short int m; int i; m = n; for (i = 15; n != 0; i--, n = n / 2) binario[i] = 0 + n % 2; printf("%9s %5d (%s)\n", msg, m, binario); } int main(void) { unsigned short int i, j, k; i = 51; j = 15; imprime(i, "i ="); imprime(j, "j ="); k = ~i; imprime(k, "~i ="); k = i & j; imprime(k, "i & j ="); k = i ^ j; imprime(k, "i ^ j ="); k = i | j; imprime(k, "i | j ="); k = i << 4; imprime(k, "i << 4 ="); k = j >> 2; imprime(k, "j >> 2 ="); return 0; }
DCT
UFMS
48.1 O PERADORES
BIT A BIT
318
i j ~i i & j i ^ j
= 51 (0000000000110011) = 15 (0000000000001111) = 65484 (1111111111001100) = 3 (0000000000000011) = 60 (0000000000111100) 63 (0000000000111111) 816 (0000001100110000) 3 (0000000000000011)
i | j = i << 4 = j >> 2 =
Os operadores sobre bits tm precedncias distintas uns sobre os outros, como mostra a tabela abaixo: Operador
~ & << >> ^ |
Observe ainda que a precedncia dos operadores sobre bits menor que precedncia dos operadores relacionais. Isso signica que um sentena como a seguir
o que muito provavelmente no a inteno do(a) programador(a) neste caso. Dessa forma, para que a inteno se concretize, devemos escrever:
Muitas vezes em programao de baixo nvel, queremos acessar bits especcos de uma seqncia de bits. Por exemplo, quando trabalhamos com computao grca, queremos colocar dois ou mais pixeis em um nico byte. Podemos extrair e/ou modicar dados que so armazenados em um nmero pequeno de bits com o uso dos operadores sobre bits.
DCT UFMS
48.1 O PERADORES
BIT A BIT
319
Suponha que i uma varivel do tipo unsigned short int , ou seja, um compartimento de memria de 16 bits que armazena um nmero inteiro. As operaes mais freqentes sobre um nico bit que podem ser realizadas sobre a varivel i so as seguintes: Ligar um bit: suponha que queremos ligar o bit 4 de i , isto o quinto bit menos signicativo, o quinto da direita para a esquerda. A forma mais fcil de ligar o bit 4 executar um OU inclusivo entre i e a constante 0x0010 :
i = i | 0x0010;
Mais geralmente, se a posio do bit que queremos ligar est armazenada na varivel j , podemos usar o operador de deslocamento esquerda e executar a seguinte expresso, seguida da atribuio:
i = i | 1 << j;
Desligar um bit: para desligar o bit 4 de i , devemos usar uma mscara com um bit 0 na posio 4 e o bit 1 em todas as outras posies:
Usando a mesma idia, podemos escrever uma sentena que desliga um bit cuja posio est armazenada em uma varivel j :
Testando um bit: o trecho de cdigo a seguir verica se o bit 4 da varivel i est ligado:
if (i & 0x0010)
if (i & 1 << j)
DCT
UFMS
48.1 O PERADORES
BIT A BIT
320
Para que o trabalho com bits torne-se um pouco mais fcil, freqentemente damos nomes s posies dos bits de interesse. Por exemplo, se os bits das posies 1, 2 e 4 correspondem s cores azul, verde e vermelho, podemos denir macros que representam essas trs posies dos bits:
Ligar, desligar e testar o bit AZUL pode ser feito como abaixo:
Trabalhar com um grupo de vrios bits consecutivos, chamados de trecho de bits1 um pouco mais complicado que trabalhar com um nico bit. As duas operaes mais comuns sobre trechos de bits so as seguintes: Modicar um trecho de bits: para alterar um trecho de bits necessrio inicialmente limp-lo usando a operao E bit a bit e posteriormente armazenar os novos bits no trecho de bits com a operao de OU inclusivo. A operao a seguir mostra como podemos armazenar o valor binrio 101 nos bits 46 da varivel i :
Suponha ainda que a varivel j contm o valor a ser armazenado nos bits 46 de i . Ento, podemos executar a seguinte sentena:
1
DCT
UFMS
48.2 T RECHOS
DE BITS EM REGISTROS
321
Recuperar um trecho de bits: quando o trecho de bits atinge o bit 0 da varivel, a recuperao de seu valor pode ser facilmente obtida da seguinte forma. Suponha que queremos recuperar os bits 02 da varivel i . Ento, podemos fazer:
j = i & 0x0007;
Se o trecho de bits no tem essa propriedade, ento podemos deslocar o trecho de bits direita para depois extra-lo usando a operao E. Para extrair, por exemplo, os bits 46 de i , podemos usar a seguinte sentena:
11
10
7
ms
2
dia
Figura 48.1: A forma de armazenamento de uma data. Usando trechos de bits, podemos denir um registro na linguagem C com o mesmo formato:
struct data { unisgned int dia: 5; unisgned int mes: 4; unisgned int ano: 7; };
DCT
UFMS
48.2 T RECHOS
DE BITS EM REGISTROS
322
O nmero aps cada campo indica o comprimento em bits desse campo. O tipo de um trecho de bits pode ser um de dois possveis: int ( signed int ) e unsigned int . O melhor declarar todos os trechos de bits que so campos de um registro como signed int ou unsigned int . Podemos usar os trechos de bits exatamente como usamos outros campos de um registro, como mostra o exemplo a seguir:
Podemos considerar que um valor armazenado no campo ano seja relativo ao ano de 19802 . Ou seja a atribuio do valor 8 ao campo ano , como feita acima, tem signicado 1988. Depois dessas atribuies, a varivel data_arquivo tem a aparncia mostrada na gura 48.2.
15 0 14 0 13 0 12 1 11 0 10 0 9 0 8 1 7 1 6 0 5 0 4 1 3 1 2 1 1 0 0 0
Figura 48.2: Varivel data_arquivo com valores armazenados. Poderamos usar operadores sobre bits para obter o mesmo resultado, talvez at mais rapidamente. No entanto, escrever um programa legvel usualmente mais importante que ganhar alguns microssegundos. Trechos de bits tm uma restrio que no se aplica a outros campos de um registro. Como os trechos de bits no tm um endereo no sentido usual, a linguagem C no nos permite aplicar o operador de endereo & para um trecho de bits. Por conta disso, funes como scanf no podem armazenar valores diretamente em um campo de um registro que um trecho de bits. claro que podemos usar scanf para ler uma entrada em uma varivel e ento atribui seu valor a um campo de um registro que um trecho de bits.
Exerccios
48.1 Uma das formas mais simples de criptografar dados usar a operao de OU exclusivo sobre cada caractere com uma chave secreta. Suponha, por exemplo, que a chave secreta o caractere &. Supondo que usamos a tabela ASCII, se zermos um OU exclusivo do caractere z com a chave, obtemos o caractere \:
XOR
cdigo binrio ASCII para & cdigo binrio ASCII para z cdigo binrio ASCII para \
1980 o ano em que o mundo comeou, segundo a Microsoft. O registro data a forma como o sistema operacional MSDOS armazena a data que um arquivo foi criado ou modicado.
DCT UFMS
48.2 T RECHOS
DE BITS EM REGISTROS
323
Para decifrar uma mensagem, aplicamos a mesma idia. Cifrando uma mensagem previamente cifrada, obtemos a mensagem original. Se executamos um OU exclusivo entre o caractere & e o caractere \, por exemplo, obtemos o caractere original z:
XOR
cdigo binrio ASCII para & cdigo binrio ASCII para \ cdigo binrio ASCII para z
Escreva um programa que receba uma cadeia de caracteres e cifre essa mensagem usando a tcnica previamente descrita. Programa 48.2: Soluo do exerccio 48.1.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
#include <ctype.h> #include <stdio.h> #define CHAVE & int main(void) { int c_orig, c_novo; c_orig = getchar(); while (c_orig != EOF) { c_novo = c_orig ^ CHAVE; if (isprint(c_orig) && isprint(c_novo)) putchar(c_novo); else putchar(c_orig); c_orig = getchar(); } return 0; }
A mensagem original pode ser digitada pelo(a) usurio ou lida a partir de um arquivo com redirecionamento de entrada. A mensagem cifrada pode ser vista na sada padro (monitor) ou pode ser armazenada em um arquivo usando redirecionamento de sada. Suponha que o programa acima tenha sido salvo como cript.c e que um executvel tenha sido gerado com nome cript . Ento, para cifrar uma mensagem (texto) digitada em um arquivo msg e armazenar o resultado no arquivo novo , podemos fazer:
DCT
UFMS
A ULA 49
I NTRODUO
AOS APONTADORES
Apontadores ou ponteiros so certamente uma das caractersticas mais destacveis da linguagem de programao C. Os apontadores agregam poder e exibilidade linguagem de maneira a diferenci-la de outras linguagens de programao de alto nvel, permitindo a representao de estruturas de dados complexas, modicao de valores passados como argumentos a funes, alocao dinmica de espaos na memria, entre outros destaques. Nesta aula iniciaremos o contato com esses elementos.
49.1 VARIVEIS
APONTADORAS
325
endereo 0 1 2 3 contedo 00010011 11010101 00111000 10010010
n1
00001111
2000 i 2001
Figura 49.2: Endereo da varivel i . Os apontadores tm um papel importante em toda essa histria. Apesar de os endereos serem representados por nmeros, como ilustrado nas guras 49.1 e 49.2, o intervalo de valores que esse objetos podem assumir diferente do intervalo que os nmeros inteiros, por exemplo, podem assumir. Isso signica, entre outras coisas, que no podemos armazenar endereos em variveis do tipo inteiro. Endereos so armazenados em variveis especiais, chamadas de variveis apontadoras. Quando armazenamos o endereo de uma varivel i em uma varivel apontadora p , dizemos que p aponta para i . Em outras palavras, um apontador nada mais que um endereo e uma varivel apontadora uma varivel que pode armazenar endereos. Ao invs de mostrar endereos como nmeros, usaremos uma notao simplicada de tal forma que, para indicar que uma varivel apontadora p armazena o endereo de uma varivel i , mostraremos o contedo de p um endereo como uma echa orientada na direo de i , como mostra a gura 49.3.
p i
DCT
UFMS
49.2 O PERADORES
DE ENDEREAMENTO E DE INDIREO
326
Uma varivel apontadora pode ser declarada da mesma forma que uma varivel qualquer, mas com um asterisco precedendo seu identicador. Por exemplo,
int *p;
Essa declarao indica que p uma varivel apontadora capaz de apontar para objetos do tipo int . A linguagem C obriga que toda varivel apontadora aponte apenas para objetos de um tipo particular, chamado de tipo referenciado. Diante do exposto at este ponto, daqui por diante no mais distinguiremos os termos apontador e varivel apontadora, cando ento subentendido valor (contedo) e varivel.
Atribuir o endereo da varivel i para a varivel p faz com que p aponte para i , como ilustra-a gura 49.4.
p
Figura 49.4: Varivel apontadora p contendo o endereo da varivel i . possvel inicializar uma varivel apontadora no momento de sua declarao, como abaixo:
int i, *p = &i;
DCT
UFMS
49.2 O PERADORES
DE ENDEREAMENTO E DE INDIREO
327
Uma vez que uma varivel apontadora aponta para um objeto, podemos usar o operador de indireo * para acessar o valor armazenado no objeto. Se p aponta para i , por exemplo, podemos imprimir o valor de i como segue:
printf("%d\n", *p);
Observe que a funo printf mostrar o valor de i e no o seu endereo. Observe tambm que aplicar o operador & a uma varivel produz um apontador para a varivel e aplicar o operador * para um apontador retoma o valor original da varivel:
j = *&i;
j = i;
Enquanto dizemos que p aponta para i , dizemos tambm que *p um apelido para i . No apenas *p tem o mesmo valor que i , mas alterar o valor de *p altera o valor de i . Uma observao importante que auxilia a escrever e ler programas com apontadores sempre traduzir os operadores unrios de endereo & e indireo * para endereo da varivel e contedo da varivel apontada por, respectivamente. Sempre que usamos um desses operadores no sentido de estabelecer indireo e apontar para valores, importante traduzi-los desta forma. Programa 49.1: Um exemplo do uso de apontadores.
1 2 3 4 5 6 7 8 9 10 11 12 13
#include <stdio.h> int main(void) { char c, *p; p = &c; c = a; printf("c=%c, *p=%c\n", c, *p); c = /; printf("c=%c, *p=%c\n", c, *p); *p = Z; printf("c=%c, *p=%c\n", c, *p); return 0; }
DCT
UFMS
49.2 O PERADORES
DE ENDEREAMENTO E DE INDIREO
328
Observe que no programa 49.1, aps a declarao das variveis c e p , temos a inicializao do apontador p , que recebe o endereo da varivel c , sendo essas duas variveis do mesmo tipo char . importante sempre destacar que o valor, ou contedo, de um apontador na linguagem C no tem signicado at que contenha, ou aponte, para algum endereo vlido. A primeira chamada da funo printf no programa 49.1 apenas mostra o contedo da varivel c , que foi inicializada com o caractere a , e tambm o contedo da varivel apontada por p . Como a varivel p aponta para a varivel c , o valor apresentado na sada tambm aquele armazenado na varivel c , isto , o caractere a . A segunda chamada da funo printf precedida pela alterao do contedo da varivel c e, como a varivel p mantm-se apontando para a varivel c , a chamada da funo printf faz com que o caractere / seja apresentado na sada. importante notar que, a menos que o contedo da varivel p seja modicado, a expresso *p sempre acessa o contedo da varivel c . Por m, a ltima chamada funo printf precedida de uma atribuio que modica o contedo da varivel apontada por p , isto , a atribuio a seguir:
*p = Z;
faz tambm com que a varivel c receba o caractere Z . A linguagem C permite ainda que o operador de atribuio copie apontadores, supondo que possuam o mesmo tipo. Suponha que a seguinte declarao tenha sido feita:
A sentena
p = &i;
um exemplo de atribuio de um apontador, onde o endereo de i copiado em p . Um outro exemplo de atribuio de apontador dado a seguir:
q = p;
Essa sentena copia o contedo de p , o endereo de i , para q , fazendo com que q aponte para o mesmo lugar que p aponta, como podemos visualizar na gura 49.5. Ambos os apontadores p e q apontam para i e, assim, podemos modicar o contedo de i indiretamente atravs da atribuio de valores para *p e *q .
DCT UFMS
49.3 A PONTADORES
EM EXPRESSES
329
?
q
#include <stdio.h> int main(void) { int i, j, *apt1, *apt2; apt1 = &i; i = 5; j = 2 * *apt1 + 3; apt2 = apt1; printf("i=%d, j=%d, *apt1=%d, *apt2=%d\n", i, j, *apt1, *apt2); return 0; }
Exerccios
49.1 Se i uma varivel e p uma varivel apontadora que aponta para i , quais das seguintes expresses so apelidos para i ? (a) *p (b) &p
DCT UFMS
49.3 A PONTADORES (c) *&p (d) &*p (e) *i (f) &i (g) *&i (h) &*i
EM EXPRESSES
330
49.2 Se i uma varivel do tipo int e p e q so apontadores para int , quais das seguintes atribuies so corretas? (a) p = i; (b) *p = &i; (c) &p = q; (d) p = &q; (e) p = *&q; (f) p = q; (g) p = *q; (h) *p = q; (i) *p = *q; 49.3 Entenda o que o programa 49.3 faz, simulando sua execuo passo a passo. Depois disso, implemente-o. Programa 49.3: Programa do exerccio 49.3.
1 2 3 4 5 6 7 8 9 10 11 12 13
#include <stdio.h> int main(void) { int a, b, *apt1, *apt2; apt1 = &a; apt2 = &b; a = 1; (*apt1)++; b = a + *apt1; *apt2 = *apt1 * *apt2; printf("a=%d, b=%d, *apt1=%d, *apt2=%d\n", a, b, *apt1, *apt2); return 0; }
49.4 Entenda o que o programa 49.4 faz, simulando sua execuo passo a passo. Depois disso, implemente-o. 49.5 Entenda o que o programa 49.5 faz, simulando sua execuo passo a passo. Depois disso, implemente-o.
DCT
UFMS
49.3 A PONTADORES
EM EXPRESSES
331
#include <stdio.h> int main(void) { int a, b, c, *apt; a = 3; b = 7; printf("a=%d, b=%d\n", a, b); apt = &a; c = *apt; apt = &b; a = *apt; apt = &c; b = *apt; printf("a=%d, b=%d\n", a, b); return 0; }
#include <stdio.h> int main(void) { int i, j, *p, *q; p = &i; q = p; *p = 1; printf("i=%d, *p=%d, *q=%d\n", i, *p, *q); q = &j; i = 6; *q = *p; printf("i=%d, j=%d, *p=%d, *q=%d\n", i, j, *p, *q); return 0; }
DCT
UFMS
A ULA 50
A PONTADORES
E FUNES
Nesta aula revemos apontadores e funes na linguagem C. At o momento, aprendemos algumas regras de como construir funes que tm parmetros de entrada e sada ou argumentos passados por referncia. Esses argumentos/parmetros, como veremos daqui por diante, so na verdade apontadores. Um endereo de uma varivel passado como argumento para uma funo. O parmetro correspondente que recebe o endereo ento um apontador. Qualquer alterao realizada no contedo do parmetro tem reexos externos funo, no argumento correspondente.
#include <stdio.h> void troca(int *a, int *b) { int aux; aux = *a; *a = *b; *b = aux; } int main(void) { int x, y; printf("Informe dois valores: "); scanf("%d%d", &x, &y); printf("Antes da troca : x = %d e y = %d\n", x, y); troca(&x, &y); printf("Depois da troca: x = %d e y = %d\n", x, y); return 0; }
332
50.1 PARMETROS
DE ENTRADA E SADA ?
333
Agora que entendemos os conceitos bsicos que envolvem os apontadores, podemos olhar o programa 50.1 e compreender o que est acontecendo, especialmente no que se refere ao uso de apontadores como argumentos de funes. Suponha que algum est executando esse programa. A execuo inicia na linha 11 da funo main e seu efeito ilustrado na gura 50.1.
Figura 50.1: Execuo da linha 11. Em seguida, uma mensagem emitida na sada aps a execuo da linha 12. Depois disso, na linha 13, suponha que o(a) usurio(a) do programa informe dois valores quaisquer do tipo inteiro como, por exemplo, 3 e 8. Isso se reete na memria como na gura 50.2.
3 x y 8
Figura 50.2: Execuo da linha 13. Na linha 14 da funo main , a execuo da funo printf permite que o(a) usurio(a) verique na sada os contedos das variveis que acabou de informar. Na linha 15 do programa, a funo troca chamada com os endereos das variveis x e y como argumentos. Isto , esses endereos so copiados nos argumentos correspondentes que compem a interface da funo. O uxo de execuo do programa ento desviado para o trecho de cdigo da funo troca . Os parmetros da funo troca so dois apontadores para valores do tipo inteiro com identicadores a e b . Esses dois parmetros recebem os endereos das variveis x e y da funo main , respectivamente, como se pode observar na gura 50.3.
Figura 50.3: Execuo da linha 15. A seguir, na linha 4 do programa, a primeira linha do corpo da funo troca , ocorre a declarao da varivel aux e um espao identicado por aux reservado na memria, como vemos na gura 50.4.
aux
50.1 PARMETROS
DE ENTRADA E SADA ?
334
Depois da declarao da varivel aux , a execuo da linha 5 faz com que o contedo da varivel apontada por a seja armazenado na varivel aux . Veja a gura 50.5.
aux
Figura 50.5: Execuo da linha 5. Na execuo da linha 6, o contedo da varivel apontada por a recebe o contedo da varivel apontada por b , como mostra a gura 50.6.
aux
Figura 50.6: Execuo da linha 6. Por m, na execuo da linha 7, o contedo da varivel apontada por b recebe o contedo da varivel aux , conforme a gura 50.7.
aux
Figura 50.7: Execuo da linha 7. Aps o trmino da funo troca , seus parmetros e variveis locais so destrudos e o uxo de execuo volta para a funo main , no ponto logo aps onde foi feita a chamada da funo troca , isto , na linha 16. A memria neste momento encontra-se no estado ilustrado na gura 50.8. Na linha 17 da funo main a chamada da funo printf , que mostra os contedo das variveis x e y , que foram trocados, conforme j constatado e ilustrado na gura 50.8. O programa ento salta para a prxima linha e chega ao m de sua execuo. Esse exemplo destaca que so realizadas cpias do valores dos argumentos que nesse
DCT UFMS
50.2 D EVOLUO
DE APONTADORES
335
Figura 50.8: Estado da memria aps o trmino da funo troca. caso so endereos das variveis da funo main para os parmetros respectivos da funo troca . No corpo dessa funo, sempre que usamos o operador de indireo para acessar algum valor, estamos na verdade acessando o contedo da varivel correspondente dentro da funo main , que chamou a funo troca . Isso ocorre com as variveis x e y da funo main , quando copiamos seus endereos nos parmetros a e b da funo troca . Cpia? Como assim cpia? Ns aprendemos que parmetros passados desta mesma forma so parmetros de entrada e sada, ou seja, so parmetros passados por referncia e no por cpia. Esse exemplo mostra uma caracterstica muito importante da linguagem C, que cou dissimulada nas aulas anteriores: s h passagem de argumentos por cpia na linguagem C, ou ainda, no h passagem de argumentos por referncia na linguagem C. O que fazemos de fato simular a passagem de um argumento por referncia usando apontadores. Assim, passando (por cpia) o endereo de uma varivel como argumento para uma funo, o parmetro correspondente deve ser um apontador e, mais que isso, um apontador para a varivel correspondente cujo endereo foi passado como argumento. Dessa forma, qualquer modicao indireta realizada no corpo dessa funo usando esse apontador ser realizada na verdade no contedo da varivel apontada pelo parmetro, que simplesmente o contedo da varivel passada como argumento na chamada da funo. No h nada de errado com o que aprendemos nas aulas anteriores sobre argumentos de entrada e sada, isto , passagem de argumentos por referncia. No entanto, vale ressaltar que passagem de argumentos por referncia um tpico conceitual quando falamos da linguagem de programao C. O correto repetir que s h passagem de argumentos por cpia na linguagem C.
1 2 3 4 5 6 7
int *max(int *a, int *b) { if (*a > *b) return a; else return b; }
DCT
UFMS
50.2 D EVOLUO
DE APONTADORES
336
Quando chamamos a funo max , passamos apontadores para duas variveis do tipo int e armazenamos o resultado em uma varivel apontadora:
Na execuo da funo max , temos que *a um apelido para i e *b um apelido para j . Se i tem valor maior que j , ento max devolve o endereo de i . Caso contrrio, max devolve o endereo de j . Depois da chamada, p aponta para i ou para j . No possvel que uma funo devolva o endereo de uma varivel local sua, j que ao nal de sua execuo, essa varivel ser destruda.
Exerccios
50.1 (a) Escreva uma funo com a seguinte interface:
void minmax(int v[], int n, int *max, int *min)
que receba um vetor v com n > 0 nmeros inteiros e devolva um maior e um menor dos elementos desse vetor. (b) Escreva um programa que receba n > 0 nmeros inteiros, armazene-os em um vetor e, usando a funo do item (a), mostre na sada um maior e um menor elemento desse conjunto. Simule no papel a execuo de seu programa antes de implement-lo. 50.2 (a) Escreva uma funo com a seguinte interface:
void doisMaiores(int v[], int n, int *p_maior, int *s_maior)
que receba um vetor v com n > 0 nmeros inteiros e devolva um maior e um segundo maior elementos desse vetor. (b) Escreva um programa que receba n > 0 nmeros inteiros, armazene-os em um vetor e, usando a funo do item (a), mostre na sada um maior e um segundo maior elemento desse conjunto. Simule no papel a execuo de seu programa antes de implement-lo.
DCT
UFMS
DE APONTADORES
337
que receba dois nmeros inteiros a e b e devolva a soma e o produto destes dois nmeros. (b) Escreva um programa que receba n nmeros inteiros, com n > 0 par, calcule a soma e o produto deste conjunto usando a funo do item (a) e determine quantos deles so maiores que esta soma e quantos so maiores que o produto. Observe que os nmeros na entrada podem ser negativos. Simule no papel a execuo de seu programa antes de implement-lo. 50.4 (a) Escreva uma funo com a seguinte interface:
int *maximo(int v[], int n)
que receba um vetor v de n nmeros inteiros e devolva o endereo do elemento de v onde reside um maior elemento de v . (b) Escreva um programa que receba n > 0 nmeros inteiros, armazene-os em um vetor e, usando a funo do item (a), mostre na sada um maior elemento desse conjunto. Simule no papel a execuo de seu programa antes de implement-lo.
DCT
UFMS
A ULA 51
A PONTADORES
E VETORES
Nas aulas 49 e 50 aprendemos o que so os apontadores e tambm como so usados como parmetros de funes e devolvidos de funes. Nesta aula veremos outra aplicao para os apontadores. A linguagem C nos permite usar expresses aritmticas de adio e subtrao com apontadores que apontam para elementos de vetores. Essa uma forma alternativa de trabalhar com vetores e seus ndices. Para nos tornarmos melhores programadores da linguagem C necessrio conhecer bem essa relao ntima entre apontadores e vetores. Alm disso, o uso de apontadores para trabalhar com vetores vantajoso em termos de ecincia do programa executvel resultante.
Podemos fazer o apontador p apontar para o elemento v[0] do vetor fazendo a seguinte atribuio, como mostra a gura 51.1:
p = &v[0];
51.1 A RITMTICA
COM APONTADORES
339
5 0 1 2 3 4 5 6 7 8 9
Figura 51.2: *p = 5; Podemos ainda executar aritmtica com apontadores ou aritmtica com endereos sobre
p e assim acessamos outros elementos do vetor v . A linguagem C possibilita trs formas de
aritmtica com apontadores: (i) adicionar um nmero inteiro a um apontador; (ii) subtrair um nmero inteiro de um apontador; e (iii) subtrair um apontador de outro apontador. Vamos olhar para cada uma dessas operaes. Suponha que temos declaradas as seguintes variveis:
Adicionar um inteiro j a um apontador p fornece um apontador para o elemento posicionado j posies aps p . Mais precisamente, se p aponta para o elemento v[i] , ento p+j aponta para v[i+j] . A gura 51.3 ilustra essa idia. Do mesmo modo, se p aponta para o elemento v[i] , ento p-j aponta para v[i-j] , como ilustrado na gura 51.4. Ainda, quando um apontador subtrado de outro, o resultado a distncia, medida em elementos do vetor, entre os apontadores. Dessa forma, se p aponta para v[i] e q aponta para v[j] , ento p q igual a i j . A gura 51.5 ilustra essa situao.
Podemos comparar variveis apontadoras entre si usando os operadores relacionais usuais ( < , <= , > , >= , == e != ). Usar os operadores relacionais para comparar dois apontadores que apontam para um mesmo vetor uma tima idia. O resultado da comparao depende das posies relativas dos dois elementos do vetor. Por exemplo, depois das atribuies dadas a seguir:
p = &v[5]; q = &v[1];
DCT
UFMS
51.1 A RITMTICA
COM APONTADORES
340
4 a
4 b
4 c
DCT
UFMS
51.1 A RITMTICA
COM APONTADORES
341
4 a
4 b
4 c
DCT
UFMS
51.2 U SO
342
Figura 51.5: p = &v[5]; e q = &v[1]; A expresso p - q tem valor 4 e a expresso q - p tem valor 4.
#define DIM 100 ... int v[DIM], soma, *p; ... soma = 0; for (p = &v[0]; p < &v[DIM]; p++) soma = soma + *p;
A condio p <&v[DIM] na estrutura de repetio for necessita de ateno especial. Apesar de estranho, possvel aplicar o operador de endereo para v[DIM] , mesmo sabendo que este elemento no existe no vetor v . Usar v[DIM] dessa maneira perfeitamente seguro, j que a sentena for no tenta examinar o seu valor. O corpo da estrutura de repetio for ser executado com p igual a &v[0] , &v[1] , . . ., &v[DIM-1] , mas quando p igual a &v[DIM] a estrutura de repetio termina. Como j vimos, podemos tambm combinar o operador de indireo * com operadores de incremento ++ ou decremento -- em sentenas que processam elementos de um vetor. Considere inicialmente o caso em que queremos armazenar um valor em um vetor e ento avanar para o prximo elemento. Usando um ndice, podemos fazer diretamente:
v[i++] = j;
Se p est apontando para um elemento de um vetor, a sentena correspondente usando esse apontador seria:
DCT UFMS
51.3 U SO
343
*p++ = j;
Devido precedncia do operador ++ sobre o operador * , o compilador enxerga essa sentena como
*(p++) = j;
O valor da expresso *p++ o valor de *p , antes do incremento. Depois que esse valor devolvido, a sentena incrementa p . A expresso *p++ no a nica combinao possvel dos operadores * e ++ . Podemos escrever (*p)++ para incrementar o valor de *p . Nesse caso, o valor devolvido pela expresso tambm *p , antes do incremento. Em seguida, a sentena incrementa *p . Ainda, podemos escrever *++p ou ainda ++*p . No primeiro caso, incrementa p e o valor da expresso *p , depois do incremento. No segundo, incrementa *p e o valor da expresso *p , depois do incremento. O trecho de cdigo acima, que realiza a soma dos elementos do vetor v usando aritmtica com apontadores, pode ento ser reescrito como a seguir, usando uma combinao dos operadores * e ++ .
int v[10];
DCT
UFMS
51.3 U SO
344
Usando v como um apontador para o primeiro elemento do vetor, podemos modicar o contedo de v[0] da seguinte forma:
*v = 7;
*(v+1) = 12;
Em geral, v+i o mesmo que &v[i] e *(v+i) equivalente a v[i] . Em outras palavras, ndices de vetores podem ser vistos como uma forma de aritmtica de apontadores. O fato de que o identicador de um vetor pode servir como um apontador facilita nossa programao de estruturas de repetio que percorrem vetores. Considere a estrutura de repetio do exemplo dado na seo anterior:
Para simplicar essa estrutura de repetio, podemos substituir &v[0] por v e &v[DIM] por v+DIM , como mostra o trecho de cdigo abaixo:
Apesar de podermos usar o identicador de um vetor como um apontador, no possvel atribuir-lhe um novo valor. A tentativa de faz-lo apontar para qualquer outro lugar um erro, como mostra o trecho de cdigo abaixo:
O programa 51.1 mostra um exemplo do uso desses conceitos, realizando a impresso dos elementos de um vetor na ordem inversa da qual forma lidos.
DCT UFMS
51.3 U SO
345
#include <stdio.h> #define N 10 int main(void) { int v[N], *p; printf("Informe %d nmeros: ", N); for (p = v; p < v+N; p++) scanf("%d", p); printf("Em ordem reversa: "); for (p = v+N-1; p >= v; p--) printf(" %d", *p); printf("\n"); return 0; }
Outro uso do identicador de um vetor como um apontador quando um vetor um argumento em uma chamada de funo. Nesse caso, o vetor sempre tratado como um apontador. Considere a seguinte funo que recebe um vetor de n nmeros inteiros e devolve um maior elemento nesse vetor.
int max(int v[], int n) { int i, maior; maior = v[0]; for (i = 1; i < n; i++) if (v[i] > maior) maior = v[i]; return maior; }
M = max(U, N);
Essa chamada faz com que o endereo do primeiro compartimento do vetor U seja atribudo v . O vetor U no de fato copiado. Para indicar que no queremos que um parmetro que um vetor no seja modicado, podemos incluir a palavra reservada const precedendo a sua declarao.
DCT UFMS
51.3 U SO
346
Quando uma varivel simples passada para uma funo, isto , quando um argumento de uma funo, seu valor copiado no parmetro correspondente. Ento, qualquer alterao no parmetro correspondente no afeta a varivel. Em contraste, um vetor usado como um argumento no est protegido contra alteraes, j que no ocorre uma cpia do vetor todo. Desse modo, o tempo necessrio para passar um vetor a uma funo independe de seu tamanho. No h perda por passar vetores grandes, j que nenhuma cpia do vetor realizada. Alm disso, um parmetro que um vetor pode ser declarado como um apontador. Por exemplo, a funo max descrita acima pode ser declarada como a seguir:
Neste caso, declarar v como sendo um apontador equivalente a declarar v como sendo um vetor. O compilador trata ambas as declaraes como idnticas. Apesar de a declarao de um parmetro como um vetor ser equivalente declarao do mesmo parmetro como um apontador, o mesmo no vale para uma varivel. A declarao a seguir:
int v[10];
faz com que o compilador reserve espao para 10 nmeros inteiros. Por outro lado, a declarao abaixo:
int *v;
faz o compilador reservar espao para uma varivel apontadora. Nesse ltimo caso, v no um vetor e tentar us-lo como tal pode causar resultados desastrosos. Por exemplo, a atribuio:
*v = 7;
armazena o valor 7 onde v est apontando. Como no sabemos para onde v est apontando, o resultado da execuo dessa linha de cdigo imprevisvel. Do mesmo modo, podemos usar uma varivel apontadora, que aponta para uma posio de um vetor, como um vetor. O trecho de cdigo a seguir ilustra essa armao.
DCT UFMS
51.3 U SO
347
#define DIM 100 ... int v[DIM], soma, *p; ... soma = 0; p = v; for (i = 0; i < DIM; i++) soma = soma + p[i];
O compilador trata p[i] como *(p+i) , que uma forma possvel de usar aritmtica com apontadores. Essa possibilidade de uso, que parece um tanto estranha primeira vista, muito til em alocao dinmica de memria, como veremos em breve.
Exerccios
51.1 Suponha que as seguintes declaraes foram realizadas:
int v[] = {5, 15, 34, 54, 14, 2, 52, 72}; int *p = &v[1], *q = &v[5];
(a) Qual o valor de *(p + 3) ? (b) Qual o valor de *(q - 3) ? (c) Qual o valor de q - p) ? (d) A expresso p < q tem valor verdadeiro ou falso? (e) A expresso *p < *q tem valor verdadeiro ou falso? 51.2 Qual o contedo do vetor v aps a execuo do seguinte trecho de cdigo?
#define N 10 int v[N] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; int *p = &v[0], *q = &v[N-1], temp; while (p < q) { temp = *p; *p++ = *q; *q-- = temp; }
51.3 Suponha que v um vetor e p um apontador. Considere que a atribuio p = v; foi realizada previamente. Quais das expresses abaixo no so permitidas? Das restantes, quais tm valor verdadeiro?
DCT UFMS
51.3 U SO
348
(a) p == v[0] (b) p == &v[0] (c) *p == v[0] (d) p[0] == v[0] 51.4 Escreva um programa que leia uma mensagem e a imprima em ordem reversa. Use a funo getchar para ler caractere por caractere, armazenando-os em um vetor. Pare quando encontrar um caractere de mudana de linha \n . Faa o programa de forma a usar um apontador, ao invs de um ndice como um nmero inteiro, para controlar a posio corrente no vetor.
DCT
UFMS
A ULA 52
A PONTADORES
E MATRIZES
Na aula 51, trabalhamos com apontadores e vetores. Nesta aula veremos as relaes entre apontadores e matrizes. Neste caso, necessrio estender o conceito de indireo (simples) para indireo dupla. Veremos tambm como estender esse conceito para indireo mltipla e suas relaes com variveis compostas homogneas multi-dimensionais.
Figura 52.1: Representao da alocao de espao na memria para uma matriz. Essa representao pode nos ajudar a trabalhar com apontadores e matrizes. Se fazemos um apontador p apontar para a primeira clula de uma matriz, isto , o elemento na posio (0,0), podemos ento visitar todos os elementos da matriz incrementando o apontador p repetidamente. Por exemplo, suponha que queremos inicializar todos os elementos de uma matriz de nmeros inteiros com 0. Suponha que temos a declarao da seguinte matriz:
int A[LINHAS][COLUNAS];
A forma que aprendemos inicializar uma matriz pode ser aplicada matriz A declarada acima e ento o seguinte trecho de cdigo realiza a inicializao desejada:
349
52.2 P ROCESSAMENTO
350
int i, j; ... for (i = 0; i < LINHAS; i++) for (j = 0; j < COLUNAS; j++) A[i][j] = 0;
Mas se vemos a matriz A da forma como armazenada na memria, isto , como um vetor unidimensional, podemos trocar o par de estruturas de repetio por uma nica estrutura de repetio:
A estrutura de repetio acima inicia com p apontando para A[0][0] . Os incrementos sucessivos de p fazem-no apontar para A[0][1] , A[0][2] , e assim por diante. Quando p atinge A[0][COLUNAS-1] , o ltimo elemento da linha 0, o prximo incremento faz com que p aponte para A[1][0] , o primeiro elemento da linha 1. O processo continua at que p ultrapasse o elemento A[LINHAS-1][COLUNAS-1] , o ltimo elemento da matriz.
p = &A[i][0];
p = A[i];
j que, para qualquer matriz A , a expresso A[i] um apontador para o primeiro elemento da linha i .
DCT UFMS
52.3 P ROCESSAMENTO
351
A justicativa para essa armao vem da aritmtica com apontadores. Lembre-se que para um vetor A , a expresso A[i] equivalente a *(A + i) . Assim, &A[i][0] o mesmo que &(*(A[i] + 0)) , que equivalente a &*A[i] e que, por m, equivalente a A[i] . No trecho de cdigo a seguir usamos essa simplicao para inicializar com zeros a linha i da matriz A :
int A[LINHAS][COLUNAS], *p, i; ... for (p = A[i]; p < A[i] + COLUNAS; p++) *p = 0;
Como A[i] um apontador para a linha i da matriz A , podemos passar A[i] para um funo que espera receber um vetor como argumento. Em outras palavras, um funo que foi projetada para trabalhar com um vetor tambm pode trabalhar com uma linha de uma matriz. Dessa forma, a funo max da aula 51 pode ser chamada com a linha i da matriz A como argumento:
M = max(A[i], COLUNAS);
int A[LINHAS][COLUNAS], (*p)[COLUNAS], j; ... for (p = &A[0]; p < A[LINHAS]; p++) (*p)[j] = 0;
Nesse exemplo, declaramos p como um apontador para um vetor de dimenso COLUNAS , cujos elementos so nmeros inteiros. Os parnteses envolvendo *p so necessrios, j que sem eles o compilador trataria p como um vetor de apontadores em vez de um apontador para um vetor. A expresso p++ avana p para a prxima linha. Na expresso (*p)[j] , *p representa uma linha inteira de A e assim (*p)[j] seleciona o elemento na coluna j da linha. Os parnteses so essenciais na expresso (*p)[i] , j que, sem eles, o compilador interpretaria a expresso como *(p[i]) .
DCT UFMS
52.4 I DENTIFICADORES
352
int A[LINHAS][COLUNAS];
Neste caso, A no um apontador para A[0][0] . Ao contrrio, um apontador para A[0] . Isso faz mais sentido se olharmos sob o ponto de vista da linguagem C, que considera A no como uma matriz bidimensional, mas como um vetor. Quando usado como um apontador, A tem tipo int (*)[COLUNAS] , um apontador para um vetor de nmeros inteiros de tamanho COLUNAS . Saber que A aponta para A[0] til para simplicar estruturas de repetio que processam elementos de uma matriz. Por exemplo, ao invs de escrever:
Com essa idia, podemos fazer o compilador acreditar que uma varivel composta homognea multi-dimensional unidimensional, isto , um vetor. Por exemplo, podemos passar a matriz A como argumento para a funo max da aula 51 da seguinte forma:
M = max(A[0], LINHAS*COLUNAS);
j que A[0 aponta para o elemento na linha 0 e coluna 0 e tem tipo int * e assim essa chamada ser executada corretamente.
DCT
UFMS
52.4 I DENTIFICADORES
353
Exerccios
52.1 Escreva uma funo que preencha uma matriz quadrada de dimenso n com a matriz identidade In . Use um nico apontador que percorra a matriz. 52.2 Reescreva a funo abaixo usando aritmtica de apontadores em vez de ndices de matrizes. Em outras palavras, elimine as variveis i e j e todos os [] . Use tambm uma nica estrutura de repetio.
int somaMatriz(const int A[DIM][DIM], int n) { int i, j, soma = 0; for (i = 0; i < n; i++) for (j = 0; j < n; j++) soma = soma + A[i][j]; return soma; }
DCT
UFMS
A ULA 53
A PONTADORES
E CADEIAS DE CARACTERES
Nas aulas 51 e 52 estudamos formas de trabalhar com variveis compostas homogneas e apontadores para seus elementos. No entanto, importante ainda estudar a relao entre apontadores e as cadeias de caracteres que, como j vimos, so vetores especiais que contm caracteres. Nesta aula aprenderemos algumas particularidades de apontadores para elementos de cadeias de caracteres na linguagem C, alm de estudar a relao entre apontadores e constantes que so cadeias de caracteres.
Nosso primeiro contato com literais foi ainda na aula 4. Literais ocorrem com freqncia na chamada das funes printf e scanf . Mas quando chamamos uma dessas funes e fornecemos uma literal como argumento, o que de fato estamos passando? Em essncia, a linguagem C trata literais como cadeias de caracteres. Quando o compilador encontra uma literal de comprimento n em um programa, ele reserva um espao de n + 1 bytes na memria. Essa rea de memria conter os caracteres da literal mais o caracter nulo que indica o nal da cadeia. O caracteres nulo um byte cujos bits so todos zeros e representado pela seqncia de caracteres \0 . Por exemplo, a literal "abc" armazenada como uma cadeia de quatro caracteres, como mostra a gura 53.1.
a b c \0
Figura 53.1: Representao de uma literal na memria. Literais podem ser vazias. A literal "" armazenada como um nico caractere nulo.
1
Frase de Bill Gates, dono da Micro$oft, em uma entrevista para Computer Magazine.
354
53.1 L ITERAIS
E APONTADORES
355
Como uma literal armazenada em um vetor, o compilador a enxerga como um apontador do tipo char * . As funes printf e scanf , por exemplo, esperam um valor do tipo char * como primeiro argumento. Se, por exemplo, fazemos a seguinte chamada:
printf("abc");
o endereo da literal "abc" passado como argumento para a funo printf , isto , o endereo de onde se encontra o caractere a na memria. Em geral, podemos usar uma literal sempre que a linguagem C permita o uso de um apontador do tipo char * . Por exemplo, uma literal pode ocorrer do lado direito de uma atribuio, como mostrado a seguir:
Essa atribuio no copia os caracteres de "abc" , mas faz o apontador p apontar para o primeiro caractere da literal, como mostra a gura 53.2.
apt
\0
Figura 53.2: Representao da atribuio de uma literal a um apontador. Observe ainda que no permitido alterar uma literal durante a execuo de um programa. Isso signica que a tentativa de modicar uma literal pode causar um comportamento indenido do programa. Relembrando, uma varivel cadeia de caracteres um vetor do tipo char que necessariamente deve reservar espao para o caractere nulo. Quando declaramos um vetor de caracteres que ser usado para armazenar cadeias de caracteres, devemos sempre declarar esse vetor com uma posio a mais que a mais longa das cadeias de caracteres possveis, j que por conveno da linguagem C, toda cadeia de caracteres nalizada com um caractere nulo. Veja, por exemplo, a declarao a seguir:
char cadeia[TAM+1];
DCT
UFMS
53.1 L ITERAIS
E APONTADORES
356
onde TAM uma macro denida com o tamanho da cadeia de caracteres mais longa que pode ser armazenada na varivel cadeia . Lembrando ainda, podemos inicializar um cadeia de caracteres no momento de sua declarao, como mostra o exemplo abaixo:
O compilador ento coloca os caracteres de "9 de outubro" no vetor data e adiciona o caractere nulo ao nal para que data possa ser usada como uma cadeia de caracteres. A gura 53.3 ilustra essa situao.
data 9 d e o u t u b r o \0
Figura 53.3: Declarao e inicializao de uma cadeia de caracteres. Apesar de "9 de outubro" se parecer com uma literal, a linguagem C de fato a v como uma abreviao para um inicializador de um vetor. Ou seja, a declarao e inicializao acima enxergada pelo compilador como abaixo:
No caso em que o inicializador menor que o tamanho denido para a cadeia de caracteres, o compilador preencher as posies nais restantes da cadeia com o caractere nulo. Por outro lado, sempre importante garantir que o inicializador tenha menor comprimento que o tamanho do vetor declarado. Tambm, podemos omitir o tamanho do vetor em uma declarao e inicializao simultneas, caso em que o vetor ter o tamanho equivalente ao comprimento do inicializador mais uma unidade, que equivale ao caractere nulo. Agora, vamos comparar a declarao abaixo:
que declara uma vetor data , que uma cadeia de caracteres, com a declarao a seguir:
DCT
UFMS
53.1 L ITERAIS
E APONTADORES
357
que declara data como um apontador. Devido relao estrita entre vetores e apontadores, podemos usar as duas verses da declarao de data como uma cadeia de caractere. Em particular, qualquer funo que receba um vetor de caracteres ou um apontador para caracteres aceita qualquer uma das verses da declarao da varivel data apresentada acima. No entanto, devemos ter cuidado para no cometer o erro de acreditar que as duas verses da declarao de data so equivalentes e intercambiveis. Existem diferenas signicativas entre as duas, que destacamos abaixo: na verso em que a varivel declarada como um vetor, os caracteres armazenados em data podem ser modicados, como fazemos com elementos de qualquer vetor; na verso em que a varivel declarada como um apontador, data aponta para uma literal que, como j vimos, no pode ser modicada; na verso com vetor, data um identicador de um vetor; na verso com apontador, data uma varivel que pode, inclusive, apontar para outras cadeias de caracteres durante a execuo do programa. Se precisamos que uma cadeia de caracteres seja modicada, nossa responsabilidade declarar um vetor de caracteres no qual ser armazenado essa cadeia. Declarar um apontador no suciente, neste caso. Por exemplo, a declarao abaixo:
char *p;
faz com que o compilador reserve espao suciente para uma varivel apontadora. Infelizmente, o compilador no reserva espao para uma cadeia de caracteres, mesmo porque, no h indicao alguma de um possvel comprimento da cadeia de caracteres que queremos armazenar. Antes de usarmos p como uma cadeia de caracteres, temos de faz-la apontar para um vetor de caracteres. Uma possibilidade fazer p apontar para uma varivel que uma cadeia de caracteres, como mostramos a seguir:
Com essa atribuio, p aponta para o primeiro caractere de cadeia e assim podemos usar p como uma cadeia de caracteres. Outra possibilidade fazer p apontar para uma cadeia de caracteres dinamicamente alocada, como veremos na aula 55. Ainda poderamos discorrer sobre processos para leitura e escrita de cadeias de caracteres, sobre acesso aos caracteres de uma cadeia de caracteres e tambm sobre o uso das funes da biblioteca da linguagem C que trabalham especicamente com cadeias de caracteres, o que j zemos nas aulas 23 e 45. Ainda veremos a seguir dois tpicos importantes sobre cadeias de caracteres: vetores de cadeias de caracteres e argumentos de linha de comando.
DCT
UFMS
53.2 V ETORES
DE CADEIAS DE CARACTERES
358
char planetas[][9] = {"Mercurio", "Venus", "Terra", "Marte", "Jupiter", "Saturno", "Urano", "Netuno", "Plutao"};
Observe que estamos omitindo o nmero de linhas da matriz, que fornecido pelo inicializador, mas a linguagem C exige que o nmero de colunas seja especicado, conforme zemos na declarao. A gura 53.4 ilustra a declarao e inicializao da varivel planetas . Observe que todas as cadeias cabem nas colunas da matriz e, tambm, que h um tanto de compartimentos desperdiados na matriz, preenchidos com o caractere \0 , j que nem todas as cadeias so compostas por 8 caracteres.
0 0 1 2 3 4 5 6 7 8 M V T M J S U N P 1 e e e a u a r e l 2 r n r r p t a t u 3 c u r t i u n u t 4 u s a e t r o n a 5 r \0 \0 \0 e n \0 o o 6 i \0 \0 \0 r o \0 \0 \0 7 o \0 \0 \0 \0 \0 \0 \0 \0 8 \0 \0 \0 \0 \0 \0 \0 \0 \0
Figura 53.4: Matriz de cadeias de caracteres planetas . A inecincia de armazenamento aparente nesse exemplo comum quando trabalhamos com cadeias de caracteres, j que colees de cadeias de caracteres sero, em geral, um misto entre curtas e longas cadeias. Uma possvel forma de sanar esse problema usar um vetor cujos elementos so apontadores para cadeias de caracteres, como podemos ver na declarao abaixo:
DCT
UFMS
53.2 V ETORES
DE CADEIAS DE CARACTERES
359
char *planetas[] = {"Mercurio", "Venus", "Terra", "Marte", "Jupiter", "Saturno", "Urano", "Netuno", "Plutao"};
Note que h poucas diferenas entre essa declarao e a declarao anterior da varivel planetas : removemos um par de colchetes com um nmero no interior deles e colocamos um asterisco precedendo o identicador da varivel. No entanto, o efeito dessa declarao na memria muito diferente, como podemos ver na gura 53.5.
planetas 0 1 2 3 4 5 6 7 8 M V T M J S U N P e e e a u a r e l r n r r p t a t u c u r t i u n u t u s a e t r o n a r \0 \0 \0 e n \0 o o \0 \0 r o \0 \0 i o \0
Figura 53.5: Vetor planetas de apontadores para cadeias de caracteres. Cada elemento do vetor planetas um apontador para uma cadeia de caracteres, terminada com um caractere nulo. No h mais desperdcio de compartimentos nas cadeias de caracteres, apesar de termos de alocar espao para os apontadores no vetor planetas . Para acessar um dos nomes dos planetas necessitamos apenas do ndice do vetor. Para acessar um caractere do nome de um planeta devemos fazer da mesma forma como acessamos um elemento em uma matriz. Por exemplo, para buscar cadeias de caracteres no vetor planetas que iniciam com a letra M, podemos usar o seguinte trecho de cdigo:
DCT
UFMS
53.3 A RGUMENTOS
NA LINHA DE COMANDOS
360
prompt$ ls
em uma linha de comando, o resultado ser uma listagem de nomes dos arquivos no diretrio atual. Se digitamos o comando seguido de uma opo, como abaixo:
prompt$ ls -l
ento o resultado uma listagem detalhada2 que nos mostra o tamanho de cada arquivo, seu proprietrio, a data e hora em que houve a ltima modicao no arquivo e assim por diante. Para modicar ainda mais o comportamento do comando ls podemos especicar que ele mostre detalhes de apenas um arquivo, como mostrado abaixo:
prompt$ ls -l exerc1.c
Nesse caso, o comando ls mostrar informaes detalhadas sobre o arquivo exerc1.c . Informaes de comando de linha esto disponveis para todos os programas, no apenas para comandos do sistema operacional. Para ter acesso aos argumentos de linha de comando, chamados de parmetros do programa na linguagem C padro, devemos denir a funo main como uma funo com dois parmetros que costumeiramente tm identicadores argc e argv . Isto , devemos fazer como abaixo:
O parmetro argc , abreviao de contador de argumentos, o nmero de argumentos de linha de comando, incluindo tambm o nome do programa. O parmetro argv , abreviao de vetor de argumentos, um vetor de apontadores para os argumentos da linha de
2
l do ingls long.
UFMS
DCT
53.3 A RGUMENTOS
NA LINHA DE COMANDOS
361
comando, que so armazenados como cadeias de caracteres. Assim, argv[0] aponta para o nome do programa, enquanto que argv[1] at argv[argc-1] apontam para os argumentos da linha de comandos restantes. O vetor argv tem um elemento adicional argv[argc] que sempre um apontador nulo, um apontador especial que aponta para nada, representado pela macro NULL . Se um(a) usurio(a) digita a linha de comando abaixo:
prompt$ ls -l exerc1.c
ento argc conter o valor 3, argv[0] apontar para a cadeia de caracteres com o nome do programa, argv[1] apontar para a cadeia de caracteres "-l" , argv[2] apontar para a cadeia de caracteres exerc1.c e argv[3] apontar para nulo. A gura 53.6 ilustra essa situao. Observe que o nome do programa no foi listado porque pode incluir o nome do diretrio ao qual o programa pertence ou ainda outras informaes que dependem do sistema operacional.
argv 0 1 2 3 e l x \0 e r c 1 . c \0
Figura 53.6: Representao de argv . Como argv um vetor de apontadores, o acesso aos argumentos da linha de comandos realizado, em geral, como mostrado na estrutura de repetio a seguir:
O programa 53.1 ilustra como acessar os argumentos de uma linha de comandos. O programa projetado para vericar se as cadeias de caracteres fornecidas na linha de comandos so nomes de planetas do sistema solar. Se o programa 53.1 tem o nome planetas.c e seu executvel correspondente tem nome planetas , ento podemos executar esse programa com uma seqncia de cadeias de caracteres, como mostramos no exemplo abaixo:
DCT UFMS
53.3 A RGUMENTOS
NA LINHA DE COMANDOS
362
#include <stdio.h> #include <string.h> #define NUM_PLANETAS 9 int main(int argc, char *argv[]) { char *planetas[] = {"Mercurio", "Venus", "Terra", "Marte", "Jupiter", "Saturno", "Urano", "Netuno", "Plutao"}; int i, j, achou; for (i = 1; i < argc; i++) { achou = 0; for (j = 0; j < NUM_PLANETAS && !achou; j++) if (strcmp(argv[i], planetas[j]) == 0) achou = 1; if (achou) printf("%s o planeta %d\n", argv[i], j+1); else printf("%s no um planeta\n", argv[i]); } return 0; }
Exerccios
53.1 As chamadas de funes abaixo supostamente escrevem um caractere de mudana de linha na sada, mas algumas delas esto erradas. Identique quais chamadas no funcionam e explique o porqu. (a) printf("%c", \n);
DCT UFMS
53.3 A RGUMENTOS
NA LINHA DE COMANDOS
363
(b) printf("%c", "\n"); (c) printf("%s", \n); (d) printf("%s", "\n"); (e) printf(\n); (f) printf("\n"); (g) putchar(\n); (h) putchar("\n"); 53.2 Suponha que declaramos um apontador p como abaixo:
char *p = "abc";
Quais das chamadas abaixo esto corretas? Mostre a sada produzida por cada chamada correta e explique por que a(s) outra(s) no est(o) correta(s). (a) putchar(p); (b) putchar(*p); (c) printf("%s", p); (d) printf("%s", *p); 53.3 Suponha que declaramos as seguintes variveis:
12abc34 56def78
qual sero os valores de i , j e s depois dessa chamada? 53.4 A funo abaixo supostamente cria uma cpia idntica de uma cadeia de caracteres. O que h de errado com a funo?
DCT
UFMS
53.3 A RGUMENTOS
NA LINHA DE COMANDOS
364
#include <stdio.h> int main(void) { char s[] = "Dvmuvsb", *p; for (p = s; *p; p++) --*p; printf("%s\n", s); return 0; }
53.6
que receba uma cadeia de caracteres (terminada com um caractere nulo) contendo caracteres arbitrrios e substitua os caracteres que so letras minsculas nessa cadeia por letras maisculas. Use cadeia apenas como vetor, juntamente com os ndices necessrios. (b) Escreva uma funo com a seguinte interface:
void maiuscula(char *cadeia)
que receba uma cadeia de caracteres (terminada com um caractere nulo) contendo caracteres arbitrrios e substitua os caracteres que so letras minsculas nessa cadeia por letras maisculas. Use apenas apontadores e aritmtica com apontadores. 53.7 (a) Escreva uma funo que receba uma cadeia de caracteres e devolva o nmero total de caracteres que ela possui. (b) Escreva uma funo que receba uma cadeia de caracteres e devolva o nmero de vogais que ela possui. (c) Escreva uma funo que receba uma cadeia de caracteres e devolva o nmero de consoantes que ela possui.
DCT UFMS
53.3 A RGUMENTOS
NA LINHA DE COMANDOS
365
(d) Escreva um programa que receba diversas cadeias de caracteres e faa a mdia do nmero de vogais, de consoantes e de smbolos de pontuao que elas possuem. Use apenas apontadores nas funes em (a), (b) e (c). 53.8 Escreva um programa que encontra a maior e a menor palavra de uma seqncia de palavras informadas pelo(a) usurio(a). O programa deve terminar se uma palavra de quatro letras for fornecida na entrada. Considere que nenhuma palavra tem mais que 20 letras. Um exemplo de entrada e sada do programa pode ser assim visualizado:
53.9 Escreva um programa com nome reverso.c que mostra os argumentos da linha de comandos em ordem inversa. Por exemplo, executando o programa da seguinte forma:
faca e garfo
53.10 Escreva um programa com nome soma.c que soma todos os argumentos informados na linha de comandos, considerando que todos eles so nmeros inteiros. Por exemplo, executando o programa da seguinte forma:
prompt$ ./soma 81 25 2
108
DCT
UFMS
A ULA 54
A PONTADORES
E REGISTROS
Nesta aula trabalharemos com apontadores e registros. Primeiro, veremos como declarar e usar apontadores para registros. Essas tarefas so equivalentes as que j zemos quando usamos apontadores para nmeros inteiros, por exemplo. Alm disso, vamos adicionar tambm apontadores como campos de registros. Quando registros contm apontadores podemos us-los como estruturas de dados poderosas tais como listas encadeadas, rvores, etc, como veremos daqui por diante.
A partir dessa denio, podemos declarar variveis do tipo struct data , como abaixo:
E ento, assim como zemos com apontadores para inteiros, caracteres e nmeros de ponto utuante, podemos declarar um apontador para o registro data da seguinte forma:
Podemos, a partir dessa declarao, fazer uma atribuio varivel p como a seguir: 366
54.1 A PONTADORES
PARA REGISTROS
367
p = &hoje;
Alm disso, podemos atribuir valores aos campos do registro de forma indireta, como fazemos abaixo:
(*p).dia = 11;
Essa atribuio tem o efeito de armazenar o nmero inteiro 11 no campo dia da varivel hoje , indiretamente atravs do apontador p no entanto. Nessa atribuio, os parnteses envolvendo *p so necessrios porque o operador . , de seleo de campo de um registro, tem maior prioridade que o operador * de indireo. importante relembrar tambm que essa forma de acesso indireto aos campos de um registro pode ser substituda, e tem o mesmo efeito, pelo operador -> como mostramos no exemplo abaixo:
p->dia = 11;
O programa 54.1 ilustra o uso de apontadores para registros. Programa 54.1: Uso de um apontador para um registro.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
#include <stdio.h> struct data { int dia; int mes; int ano; }; int main(void) { struct data hoje, *p; p = &hoje; p->dia = 13; p->mes = 9; p->ano = 2007; printf("A data de hoje %d/%d/%d\n", hoje.dia, hoje.mes, hoje.ano); return 0; }
Na linha 9 h a declarao de duas variveis: um registro com identicador hoje e um apontador para registros com identicador p . Na linha 10, p recebe o endereo da varivel
DCT UFMS
54.2 R EGISTROS
CONTENDO APONTADORES
368
hoje . Observe que a varivel hoje do tipo struct data , isto , a varivel hoje do mesmo tipo da varivel p e, portanto, essa atribuio vlida. Em seguida, valores do tipo inteiro so armazenados na varivel hoje , mas de forma indireta, com uso do apontador p . Por m, os valores atribudos so impressos na sada. A gura 54.1 mostra as variveis hoje e p depois das atribuies realizadas durante a execuo do programa 54.1.
dia p mes ano
13
9
hoje
2004
A partir dessa denio, podemos declarar variveis (registros) do tipo struct reg_apts como a seguir:
Em seguida, a varivel bloco pode ser usada como sempre zemos. Note apenas que bloco no um apontador, mas um registro que contm dois campos que so apontadores. Veja o programa 54.2, que mostra o uso dessa varivel. Observe atentamente a diferena entre (*p).dia e *reg.apt1 . No primeiro caso, p um apontador para um registro e o acesso indireto a um campo do registro, via esse apontador, tem de ser feito com a sintaxe (*p).dia , isto , o contedo do endereo contido em p um registro e, portanto, a seleo do campo descrita fora dos parnteses. No segundo caso, reg um registro e no um apontador para um registro e como contm campos que so apontadores, o acesso ao contedo dos campos realizado atravs do operador de indireo * . Assim, *reg.apt1 signica que queremos acessar o contedo do endereo apontado por reg.apt1 . Como o operador de seleo de campo . de um registro tem prioridade pelo operador de indireo * , no h necessidade de parnteses, embora pudssemos us-los da forma *(reg.apt1) . A gura 54.2 ilustra essa situao.
DCT UFMS
54.2 R EGISTROS
CONTENDO APONTADORES
369
#include <stdio.h> struct apts_int { int *apt1; int *apt2; }; int main(void) { int i1, i2; struct apts_int reg; i2 = 100; reg.apt1 = &i1; reg.apt2 = &i2; *reg.apt1 = -2; printf("i1 = %d, *reg.apt1 = %d\n", i1, *reg.apt1); printf("i2 = %d, *reg.apt2 = %d\n", i2, *reg.apt2); return 0; }
apt1 apt2 i1
2
reg
100
i2
Exerccios
54.1 Qual a sada do programa descrito abaixo?
1 2 3 4 5 6 7 8 9 10 11 12 13
#include <stdio.h> struct dois_valores { int vi; float vf; }; int main(void) { struct dois_valores reg1 = {53, 7.112}, reg2, *p = ®1; reg2.vi = (*p).vf; reg2.vf = (*p).vi; printf("1: %d %f\n2: %d %f\n", reg1.vi, reg1.vf, reg2.vi, reg2.vf); return 0; }
DCT
UFMS
54.2 R EGISTROS
CONTENDO APONTADORES
370
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
#include <stdio.h> struct apts { char *c; int *i; float *f; }; int main(void) { char caractere; int inteiro; float real; struct apts reg; reg.c = &caractere; reg.i = &inteiro; reg.f = ℜ scanf("%c%d%f", reg.c, reg.i, reg.f); printf("%c\n%d\n%f\n", caractere, inteiro, real); return 0; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
#include <stdio.h> struct celula { int valor; struct celula *prox; }; int main(void) { struct celula reg1, reg2, *p; scanf("%d%d", ®1.valor, ®2.valor); reg1.prox = ®2; reg2.prox = NULL; for (p = ®1; p != NULL; p = p->prox) printf("%d ", p->valor); printf("\n"); return 0; }
DCT
UFMS
A ULA 55
U SO AVANADO DE APONTADORES
Nas aulas 49 a 54 vimos formas importantes de uso de apontadores: como parmetros de funes simulando passagem por referncia e como elementos da linguagem C que podem acessar indiretamente outros compartimentos de memria, seja uma varivel, uma clula de um vetor ou de uma matriz ou ainda um campo de um registro, usando, inclusive uma aritmtica especca para tanto. Nesta aula veremos outros usos para apontadores: como auxiliares na alocao dinmica de espaos de memria, como apontadores para funes e como apontadores para outros apontadores.
Nesse caso, temos a declarao de um vetor com identicador vet e 100 posies de memria que podem armazenar nmeros inteiros e uma matriz com identicador mat de 40 linhas e 60 colunas que so compartimentos de memria que podem armazenar nmeros de ponto utuante. Todos os compartimentos dessas variveis compostas homogneas cam disponveis para uso durante a execuo do programa. 371
55.1 A LOCAO
DINMICA DE MEMRIA
372
Em diversas aplicaes, para que os dados de entrada sejam armazenados em variveis compostas homogneas com dimenso(es) adequadas, necessrio saber antes essas dimenso(es), o que ento solicitado a um(a) usurio(a) do programa logo de incio. Por exemplo, o(a) usurio(a) da aplicao pode querer informar a quantidade de elementos que ser armazenada no vetor vet , um valor que ser mantido no programa para vericao do limite de armazenamento e que no deve ultrapassar o limite mximo de 100 elementos. Note que a previso de limitante mximo deve sempre ser especicada tambm. Do mesmo modo, o(a) usurio(a) pode querer informar, antes de usar essa estrutura, quantas linhas e quantas colunas da matriz mat sero usadas, sem ultrapassar o limite mximo de 40 linhas e 60 colunas. Se o(a) usurio(a), por exemplo, usar apenas 10 compartimentos do vetor vet ou apenas 3 3 compartimentos da matriz mat durante a execuo do programa, os compartimentos restantes no sero usados, embora tenham sido alocados na memria durante suas declaraes, ocupando espao desnecessrio na memria do sistema computacional. Essa alocao que acabamos de descrever e que conhecemos bem chamada de alocao esttica de memria, o que signica que, antes da execuo, o compilador, quando encontra uma declarao como essa, reserva na memria um nmero xo de compartimentos correspondentes declarao. Esse espao xo e no pode ser alterado durante a execuo do programa. Alocao esttica e compartimentos no usados na memria podem no ter impacto signicativo em programas pequenos, que fazem pouco uso da memria, como os nossos programas desenvolvidos at aqui. No entanto, devemos sempre ter em mente que a memria um recurso limitado e que em programas maiores e que armazenam muitas informaes em memria temos, de alguma forma, de us-la de maneira eciente, economizando compartimentos sempre que possvel. Nesse sentido, se pudssemos declarar variveis compostas homogneas com o nmero exato de compartimentos que sero de fato usados durante a execuo do programa, ento no haveria esse desperdcio mencionado. No exemplo da declarao de vet e mat , economizaramos espao signicativo de memria, caso necessitssemos apenas de 10 compartimentos no vetor vet e 9 compartimentos na matriz mat . No entanto, em outra execuo subseqente do mesmo programa que declara essas variveis, podem ser necessrias capacidades diferentes para ambas as variveis. Dessa forma, xar um valor torna-se invivel e o que melhor podemos fazer em alocao esttica de memria e prever um limitante mximo para esssas quantidades, conforme vimos fazendo at aqui. Felizmente para ns, programadores e programadoras da linguagem C, possvel alocar dinamicamente um ou mais blocos de memria na linguagem C. Alocao dinmica de memria signica que um programa solicita ao sistema computacional, durante a sua execuo, blocos da memria principal que estejam disponveis para uso. Caso haja espao suciente disponvel na memria principal, a solicitao atendida e o espao solicitado ca reservado na memria para aquele uso especco. Caso contrrio, isto , se no h como atender a solicitao de espao, uma mensagem de erro em tempo de execuo emitida e o(a) programador(a) tem de estar preparado para esse tipo de situao, incluindo testes de vericao de situaes como essa no arquivo-fonte, informando ao() usurio(a) a impossibilidade de uso do espao solicitado de memria, solicitando ainda alguma deciso do(a) usurio(a) e, provavelmente, encerrando a execuo do programa. Vejamos um exemplo no programa 55.1 que faz alocao dinmica de um vetor.
DCT
UFMS
55.1 A LOCAO
DINMICA DE MEMRIA
373
#include <stdio.h> #include <stdlib.h> int main(void) { int i, n; int *vetor, *apt; printf("Informe a dimenso do vetor: "); scanf("%d", &n); vetor = (int *) malloc(n * sizeof(int)); if (vetor != NULL) { for (i = 0; i < n; i++) { printf("Informe o elemento %d: ", i+1); scanf("%d", (vetor+i)); } printf("\nVetor : "); for (apt = vetor; apt < (vetor+n); apt++) printf("%d ", *apt); printf("\nVetor invertido: "); for (i = n-1; i >= 0; i--) printf("%d ", vetor[i]); printf("\n"); } else printf("Impossvel alocar o espao requisitado\n"); return 0; }
Na segunda linha do programa 55.1, inclumos o arquivo-cabealho stdlib.h , que contm a declarao da funo malloc , usada na linha 9 desse programa. A funo malloc tem sua interface apresentada a seguir:
A funo malloc reserva uma certa quantidade especca de memria e devolve um apontador do tipo void . No programa acima, reservamos n compartimentos contnuos que podem armazenar nmeros inteiros, o que se reete na expresso n * sizeof(int) . Essa expresso, na verdade, reete o nmero de bytes que sero alocados continuamente na memria, que depende do sistema computacional onde o programa compilado. O operador unrio sizeof devolve como resultado o nmero de bytes dados pelo seu operando, que pode ser um tipo de dados ou uma expresso. Nesse caso, nas mquinas que usamos no laboratrio, a expresso sizeof(int) devolve 4 bytes. Esse nmero multiplicado por n, o nmero de compartimentos que desejamos para armazenar nmeros inteiros. O endereo da primeira posio de
DCT UFMS
55.1 A LOCAO
DINMICA DE MEMRIA
374
memria onde encontram-se esses compartimentos devolvido pela funo malloc . Essa funo devolve um apontador do tipo void . Por isso, usamos o modicador de tipo (int *) para indicar que o endereo devolvido de fato um apontador para um nmero inteiro. Por m, esse endereo armazenado em vetor , que foi declarado como um apontador para nmeros inteiros. A partir da, podemos usar cmdprgvet da forma como preferirmos, como um apontador ou como um vetor. O programa 55.2 um exemplo de alocao dinmica de memria de uma matriz de nmeros inteiros. Esse programa um pouco mais complicado que o programa 55.1, devido ao uso distinto que faz da funo malloc . Programa 55.2: Um exemplo de alocao dinmica de uma matriz.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
#include <stdio.h> #include <stdlib.h> int main(void) { int i, j, m, n, **matriz, **apt; printf("Informe a dimenso da matriz: "); scanf("%d %d", &m, &n); matriz = (int **) malloc(m * sizeof(int *)); if (matriz == NULL) { printf("No h espao suficiente na memria\n"); return 0; } for (apt = matriz, i = 0; i < m; i++, apt++) { *apt = (int *) malloc(n * sizeof(int)); if (*apt == NULL) { printf("No h espao suficiente na memria\n"); return 0; } } for (i = 0; i < m; i++) for (j = 0; j < n; j++) { printf("Informe o elemento (%d,%d): ", i+1, j+1); scanf("%d", &matriz[i][j]); } printf("\nMatriz:\n"); apt = matriz; for (i = 0; i < m; i++) { for (j = 0; j < n; j++) printf("%d ", *(*(apt+i)+j)); printf("\n"); } printf("\n"); return 0; }
DCT
UFMS
55.1 A LOCAO
DINMICA DE MEMRIA
375
Observe por m que as linhas em que ocorrem a alocao dinmica de memria no programa 55.2 podem ser substitudas de forma equivalente pelas linhas a seguir:
matriz = (int **) malloc(m * sizeof(int *)); for (i = 0; i < m; i++) matriz[i] = (int *) malloc(n * sizeof(int));
Alm da funo malloc existem duas outras funes para alocao de memria na stdlib.h : calloc e realloc , mas a primeira mais freqentemente usada que essas outras duas. Essas funes solicitam blocos de memria de um espao de armazenamento conhecido como heap1 ou ainda lista de espaos disponveis. A chamada freqente dessas funes pode exaurir o heap do sistema, fazendo com que essas funes devolvam um apontador nulo. Pior ainda, um programa pode alocar blocos de memria e perd-los de algum modo, gastando espao desnecessrio. Considere o exemplo a seguir:
p = malloc(...); q = malloc(...); p = q;
Aps as duas primeiras sentenas serem executadas, p aponta para um bloco de memria e q aponta para um outro. No entanto, aps a atribuio de q para p na ltima sentena, as duas variveis apontam para o mesmo bloco de memria, o segundo bloco, sem que nenhuma delas aponte para o primeiro. Alm disso, no poderemos mais acessar o primeiro bloco, que car perdido na memria, ocupando espao desnecessrio. Esse bloco chamado de lixo. A funo free usada para ajudar os programadores da linguagem C a resolver o problema de gerao de lixo na memria durante a execuo de programas. Essa funo tem a seguinte interface na stdlib.h :
Para usar a funo free corretamente, devemos lhe passar um apontador para um bloco de memria que no mais necessitamos, como fazemos abaixo:
Uma traduo possvel do ingls pode ser amontoado ou coleo de coisas dispostas juntas.
UFMS
DCT
55.2 A PONTADORES
PARA APONTADORES
376
A chamada funo free devolve o bloco de memria apontado por p para o heap, que ca disponvel para uso em chamadas subseqentes das funes de alocao de memria. O argumento da funo free deve ser um apontador que foi previamente devolvido por uma funo de alocao de memria.
DCT
UFMS
55.3 A PONTADORES
PARA FUNES
377
#include <stdio.h> int main(void) { int x, y, *apt1, *apt2, **aptapt1, **aptapt2; x = 1; y = 4; printf("x=%d y=%d\n", x, y); apt1 = &x; apt2 = &y; printf("*apt1=%d *apt2=%d\n", *apt1, *apt2); aptapt1 = &apt1; aptapt2 = &apt2; printf("**aptapt1=%d **aptapt2=%d\n", **aptapt1, **aptapt2); return 0; }
fazem das variveis aptapt1 e aptapt2 apontadores para apontadores. Ou seja, aptapt1 contm o endereo da varivel apt1 e aptapt2 contm o endereo da varivel apt2 . Por sua vez, a varivel apt1 contm o endereo da varivel x e a varivel apt2 contm o endereo da varivel y , o que caracteriza as variveis aptapt1 e aptapt2 como apontadores para apontadores. Observe nalmente que para acessar o contedo do endereo apontado pelo endereo apontado por aptapt1 temos de usar o smbolo de indireo dupla ** , como pode ser vericado na ltima chamada funo printf do programa. Veja a gura 55.1 para um exemplo esquemtico do que ocorre na memria quando apontadores para apontadores so usados em um programa.
1
aptapt1 apt1 x y
4
apt2 aptapt2
55.3 A PONTADORES
PARA FUNES
378
Podemos usar apontadores para funes assim como usamos apontadores para variveis. Em particular, passar um apontador para uma funo como um argumento de outra funo bastante comum na linguagem C. Suponha que estamos escrevendo a funo integral que integra uma funo matemtica f entre os pontos a e b . Gostaramos de fazer a funo integral to geral quanto possvel, passando a funo f como um argumento seu. Isso possvel na linguagem C pela denio de f como um apontador para uma funo. Considerando que queremos integrar funes que tm um parmetro do tipo double e que devolvem um valor do tipo double , uma possvel interface da funo integral apresentada a seguir:
Os parnteses em torno de *f indicam que f um apontador para uma funo, no uma funo que devolve um apontador. Tambm permitido denir f como se fosse uma funo:
Do ponto de vista do compilador, as interfaces acima so idnticas. Quando chamamos a funo integral devemos fornecer um nome de uma funo como primeiro argumento. Por exemplo, a chamada a seguir integra a funo seno de 0 a /2:
O argumento sin e o nome da funo seno includa em math.h . Observe que no h parnteses aps sin . Quando o nome de uma funo no seguido por parnteses, o compilador produz um apontador para a funo em vez de gerar cdigo para uma chamada da funo. No exemplo acima, no h uma chamada funo sin . Ao invs disso, estamos passando para a funo integral um apontador para a funo sin . Podemos pensar em apontadores para funes como pensamos com apontadores para vetores e matrizes. Relembrando, se, por exemplo, v o identicador de um vetor, ento v[i] representa um elemento do vetor enquanto que v representa um apontador para o vetor, ou melhor, para o primeiro elemento do vetor. Da forma similar, se f o identicador de uma funo, a linguagem C trata f(x) como uma chamada da funo, mas trata f como um apontador para a funo. Dentro do corpo da funo integral podemos chamar a funo apontada por f da seguinte forma:
y = (*f)(x);
DCT
UFMS
55.3 A PONTADORES
PARA FUNES
379
Nessa chamada, *f representa a funo apontada por f e x o argumento dessa chamada. Assim, durante a execuo da chamada integral(sin, 0.0, PI / 2) , cada chamada de *f , na verdade, uma chamada de sin . Tambm podemos armazenar apontadores para funes em variveis ou us-los como elementos de um vetor, de uma matriz, de um campo de um registro. Podemos ainda escrever funes que devolvem apontadores para funes. Como um exemplo, declaramos abaixo uma varivel que pode armazenar um apontador para uma funo:
void (*aptf)(int);
O apontador aptf pode apontar para qualquer funo que tenha um nico parmetro do tipo int e que devolva um valor do tipo void . Se f uma funo com essas caractersticas, podemos fazer aptf apontar para f da seguinte forma:
aptf = f;
Observe que no h operador de endereamento antes de f . Uma vez que pf aponta para f , podemos chamar f indiretamente atravs de aptf como a seguir:
(*aptf)(i);
O programa 55.4 imprime uma tabela mostrando os valores das funes seno, cosseno e tangente no intervalo e incremento escolhidos pelo(a) usurio(a). O programa usa as funes sin , cos e tan de math.h .
DCT
UFMS
55.3 A PONTADORES
PARA FUNES
380
#include <math.h> #include <stdio.h> void tabela(double (*f)(double), double a, double b, double incr) { int i, num_intervalos; double x; num_intervalos = ceil((b - a) / incr); for (i = 0; i <= num_intervalos; i++) { x = a + i * incr; printf("%11.6f %11.6f\n", x, (*f)(x)); } } int main(void) { double inicio, fim, incremento; printf("Informe um intervalo [a, b]: "); scanf("%lf%lf", &inicio, &fim); printf("Informe o incremento: "); scanf("%lf", &incremento); printf("\n x cos(x)" "\n -----------------\n"); tabela(cos, inicio, fim, incremento); printf("\n x sen(x)" "\n -----------------\n"); tabela(sin, inicio, fim, incremento); printf("\n x tan(x)" "\n -----------------\n"); tabela(tan, inicio, fim, incremento); return 0; }
DCT
UFMS
55.3 A PONTADORES
PARA FUNES
381
Exerccios
55.1 Refaa o exerccio 21.1 usando alocao dinmica de memria. 55.2 Refaa o exerccio 22.2 usando alocao dinmica de memria. 55.3 Refaa o exerccio 23.2 usando alocao dinmica de memria. 55.4 Refaa o exerccio 24.3 usando alocao dinmica de memria. 55.5 Refaa o exerccio 26.1 usando alocao dinmica de memria. 55.6 Simule a execuo do programa a seguir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
#include <stdio.h> int f1(int (*f)(int)) { int n = 0; while ((*f)(n)) n++; return n; } int f2(int i) { return i * i + i - 12; } int main(void) { printf("Resposta: %d\n", f1(f2)); return 0; }
+ g(j) .
DCT
UFMS
A ULA 56
A RQUIVOS
Nos programas que zemos at aqui, a entrada e a sada sempre ocorreram em uma janela de terminal ou console do sistema operacional. Na linguagem C, no existem palavras-chaves denidas para tratamento de operaes de entrada e sada. Essas tarefas so realizadas atravs de funes. Pouco usamos outras funes de entrada e sada da linguagem C alm das funes scanf e printf , localizadas na biblioteca padro de entrada e sada, com arquivo-cabealho stdio.h . Nesta aula, aprenderemos funes que realizam tarefas de entrada e sada em arquivos.
Veremos mais sobre arquivos do sistema na seo 56.3.5. A biblioteca representada pelo arquivo-cabealho stdio.h suporta dois tipos de arquivos: texto e binrio. Os bytes em um arquivo-texto representam caracteres, fazendo que seja possvel examinar e editar o seu contedo. O arquivo-fonte de um programa na linguagem C, por exemplo, armazenado em um arquivo-texto. Por outro lado, os bytes em um arquivo-binrio no representam necessariamente caracteres. Grupos de bytes podem representar outros tipos 382
56.2 R EDIRECIONAMENTO
DE ENTRADA E SADA
383
de dados tais como inteiros e nmeros com ponto utuante. Um programa executvel, por exemplo, armazenado em um arquivo-binrio. Arquivos-texto possuem duas principais caractersticas que os diferem dos arquivosbinrios: so divididos em linhas e podem conter um marcador especial de m de arquivo. Cada linha do arquivo-texto normalmente termina com um ou dois caracteres especiais, dependendo do sistema operacional.
#include <stdio.h> int main(void) { int pot10, numdec, numbin; scanf("%d", &numdec); pot10 = 1; numbin = 0; while (numdec > 0) { numbin = numbin + (numdec % 2) * pot10; numdec = numdec / 2; pot10 = pot10 * 10; } printf("%d\n", numbin); return 0; }
Supondo que o programa 56.1 tenha sido armazenado no arquivo-fonte decbin.c e o programa executvel equivalente tenha sido criado aps a compilao com o nome decbin , ento, se queremos que a sada do programa executvel decbin seja armazenada no arquivo resultado , ento podemos digitar em uma linha de terminal o seguinte:
DCT
UFMS
56.3 F UNES
384
Esse comando instrui o sistema operacional a executar o programa decbin redirecionando a sada, que normalmente seria apresentada no terminal, para um arquivo com nome resultado . Dessa forma, qualquer informao a ser apresentada por uma funo de sada, como printf , no ser mostrada no terminal, mas ser escrita no arquivo resultado . Por outro lado, podemos redirecionar a entrada de um programa executvel, de tal forma que chamadas a funes que realizam entrada de dados, no mais solicitem essas informaes ao usurio a partir do terminal, mas as obtenha a partir de um arquivo. Por exemplo, o programa 56.1 usa a funo scanf para ler um nmero inteiro a partir de um terminal. Podemos redirecionar a entrada do programa decbin quando est sendo executado, fazendo que essa entrada seja realizada a partir de um arquivo. Por exemplo, se temos um arquivo com nome numero que contm um nmero inteiro, podemos digitar o seguinte em uma linha de terminal:
Com esse redirecionamento, o programa 56.1, que solicita um nmero a ser informado pelo usurio, no espera at que um nmero seja digitado. Ao contrrio, pelo redirecionamento, a entrada do programa tomada do arquivo numero . Ou seja, a chamada funo scanf tem o efeito de ler um valor do arquivo numero e no do terminal, embora a funo scanf no saiba disso. Podemos redirecionar a entrada e a sada de um programa simultaneamente da seguinte maneira:
Esse comando faz com que o programa decbin seja executado tomando a entrada de dados a partir do arquivo resultado e escrevendo a sada no arquivo resultado . O redirecionamento de entrada e sada uma ferramenta til, j que, podemos manter um arquivo de entradas e realizar diversos testes sobre um programa executvel a partir desse arquivo de entradas. Alm disso, se temos, por exemplo, um arquivo alvo que contm solues correspondentes s entradas, podemos comparar esse arquivo de solues com as sadas de nosso programa, que tambm podem ser armazenadas em um arquivo.
56.3 F UNES
385
de/para dois ou mais arquivos. Os arquivos so mantidos em um dispositivo do sistema computacional conhecido como memria secundria, que pode ser implementado como um disco rgido, um disquete, um disco compacto (CD), um disco verstil digital (DVD), um carto de memria, um disco amovvel (USB ash memory), entre outros. A linguagem C tem um conjunto de funes especcas para tratamento de arquivos que se localiza na biblioteca padro de entrada e sada, cujo arquivo-cabealho stdio.h .
Descrio modo de leitura de texto modo de escrita de texto modo de adicionar texto modo de leitura e escrita de texto modo de leitura e escrita de texto modo de leitura e escrita de texto modo de leitura em binrio modo de escrita em binrio modo de adicionar em binrio modo de leitura e escrita em binrio modo de leitura e escrita em binrio modo de leitura e escrita em binrio
onde:
DCT UFMS
56.3 F UNES
386
trunca o arquivo existente com tamanho 0 ou cria novo arquivo; abre ou cria o arquivo e posiciona o apontador no nal do arquivo.
Algumas observaes sobre os modos de abertura de arquivos se fazem necessrias. Primeiro, observe que se o arquivo no existe e aberto com o modo de leitura ( r ) ento a abertura falha. Tambm, se o arquivo aberto com o modo de adicionar ( a ), ento todas as operaes de escrita ocorrem o nal do arquivo, desconsiderando a posio atual do apontador do arquivo. Por m, se o arquivo aberto no modo de atualizao ( + ) ento a operao de escrita no pode ser imediatamente seguida pela operao de leitura, e vice-versa, a menos que uma operao de reposicionamento do apontador do arquivo seja executada, tal como uma chamada a qualquer uma das funes fseek , fsetpos , rewind ou fflush . Por exemplo, o comando de atribuio a seguir
tem o efeito de abrir um arquivo com nome entrada no modo de leitura. A chamada funo fopen devolve um identicador para o arquivo aberto que atribudo ao apontador aptarq do tipo FILE . Ento, esse apontador posicionado no primeiro caracter do arquivo. A declarao prvia do apontador aptarq deve ser feita da seguinte forma:
FILE *aptarq;
A funo fclose faz o oposto que a funo fopen faz, ou seja, informa o sistema que o programador no necessita mais usar o arquivo. Quando um arquivo fechado, o sistema realiza algumas tarefas importantes, especialmente a escrita de quaisquer dados que o sistema possa ter mantido na memria principal para o arquivo na memria secundria, e ento dissocia o identicador do arquivo. Depois de fechado, no podemos realizar tarefas de leitura ou escrita no arquivo, a menos que seja reaberto. A funo fclose tem a seguinte interface:
Se a operao de fechamento do arquivo apontado por aptarq obtm sucesso, a funo fclose devolve o valor 0 (zero). Caso contrrio, o valor EOF devolvido.
56.3 F UNES
387
A funo fgetc l o prximo caracter do arquivo apontado por aptarq , avanando esse apontador em uma posio. Se a leitura realizada com sucesso, o caracter lido devolvido pela funo. Note, no entanto, que a funo, ao invs de especicar o valor de devoluo como sendo do tipo unsigned char , especica-o como sendo do tipo int . Isso se deve ao fato de que a leitura pode falhar e, nesse caso, o valor devolvido o valor armazenado na constante simblica EOF , denida no arquivo-cabealho stdio.h . O valor correspondente constante simblica EOF obviamente um valor diferente do valor de qualquer caracter e, portanto, um valor negativo. Do mesmo modo, se o m do arquivo encontrado, a funo fgetc tambm devolve EOF . A funo fputc da biblioteca padro de entrada e sada da linguagem C permite que um nico caracter seja escrito em um arquivo. A interface dessa funo apresentada a seguir:
Se a funo fputc tem sucesso, o apontador aptarq incrementado e o caracter escrito devolvido. Caso contrrio, isto , se ocorre um erro, o valor EOF devolvido. Existe outro par de funes de leitura e escrita em arquivos com identicadores fscanf e fprintf . As interfaces dessas funes so apresentadas a seguir:
Essas duas funes so semelhantes s respectivas funes scanf e printf que conhecemos bem, a menos de um parmetro a mais que informado, justamente o primeiro, que o apontador para o arquivo que ser quer realizar as operaes de entrada e sada formatadas. Dessa forma, o exemplo de chamada a seguir
DCT
UFMS
56.3 F UNES
388
realiza a escrita no arquivo apontado por aptarq da mensagem entre aspas duplas, substituindo o valor numrico correspondente armazenado na varivel numero . O nmero de caracteres escritos no arquivo devolvido pela funo fprintf . Se um erro ocorrer, ento o valor -1 devolvido. Do mesmo modo,
realiza a leitura de um valor que ser armazenado na varivel numero a partir de um arquivo identicado pelo apontador aptarq . Se a leitura for realizada com sucesso, o nmero de valores lidos pela funo fscanf devolvido. Caso contrrio, isto , se houver falha na leitura, o valor EOF devolvido. H outras funes para entrada e sada de dados a partir de arquivos, como as funes
fread e fwrite , que no sero cobertas nesta aula. O leitor interessado deve procurar as
onde aptarq o apontador para um arquivo. Se o apontador aptarq contm um valor nulo, ento todos espaos de armazenamento temporrios na memria principal de todos os arquivos abertos so descarregados nos dispositivos de memria secundria. Se a descarga realizada com sucesso, a funo devolve o valor 0 (zero). Caso contrrio, a funo devolve o valor EOF . Sobre as funes que tratam do posicionamento do apontador de um arquivo, existe uma funo especca da biblioteca padro da linguagem C que realiza um teste de nal de arquivo. Essa funo tem identicador feof e a seguinte interface:
DCT
UFMS
56.3 F UNES
389
O argumento da funo feof um apontador para um arquivo do tipo FILE . A funo devolve um valor inteiro diferente de 0 (zero) se o apontador aptarq est posicionado no nal do arquivo. Caso contrrio, a funo devolve o valor 0 (zero). A funo fgetpos determina a posio atual do apontador do arquivo e tem a seguinte interface:
onde aptarq o apontador associado a um arquivo e pos uma varivel que, aps a execuo dessa funo, conter o valor da posio atual do apontador do arquivo. Observe que fpos_t um novo tipo de dado, denido no arquivo-cabealho stdio.h , adequado para armazenamento de uma posio qualquer de um arquivo. Se a obteno dessa posio for realizada com sucesso, a funo devolve 0 (zero). Caso contrrio, a funo devolve um valor diferente de 0 (zero). A funo fsetpos posiciona o apontador de um arquivo em alguma posio escolhida e tem a seguinte interface:
onde aptarq o apontador para um arquivo e pos uma varivel que contm a posio para onde o apontador do arquivo ser deslocada. Se a determinao dessa posio for realizada com sucesso, a funo devolve 0 (zero). Caso contrrio, a funo devolve um valor diferente de 0 (zero). A funo ftell determina a posio atual de um apontador em um dado arquivo. Sua interface apresentada a seguir:
A funo ftell devolve a posio atual no arquivo apontado por aptarq . Se o arquivo binrio, ento o valor o nmero de bytes a partir do incio do arquivo. Se o arquivo de texto, ento esse valor pode ser usado pela funo fseek , como veremos a seguir. Se h sucesso na sua execuo, a funo devolve a posio atual no arquivo. Caso contrrio, a funo devolve o valor -1L . A funo fseek posiciona o apontador de um arquivo para uma posio determinada por um deslocamento. A interface da funo dada a seguir:
DCT
UFMS
56.3 F UNES
390
O argumento aptarq o apontador para um arquivo. O argumento desloca o nmero de bytes a serem saltados a partir do contedo do argumento apartir . Esse contedo pode ser um dos seguintes valores pr-denidos no arquivo-cabealho stdio.h :
SEEK_SET SEEK_CUR SEEK_END
Em um arquivo de texto, o contudo de apartir dever ser SEEK_SET e o contedo de desloca deve ser 0 (zero) ou um valor devolvido pela funo ftell . Se a funo executada com sucesso, o valor 0 (zero) devolvido. Caso contrrio, um valor diferente de 0 (zero) devolvido. A funo rewind faz com que o apontador de um arquivo seja posicionado para o incio desse arquivo. A interface dessa funo a seguinte:
A funo remove elimina um arquivo, com nome armazenado na cadeia de caracteres nome , do sistema de arquivos do sistema computacional. O arquivo no deve estar aberto no programa. Se a remoo realizada, a funo devolve o valor 0 (zero). Caso contrrio, um valor diferente de 0 (zero) devolvido. A funo rename tem a seguinte interface:
A funo rename faz com que o arquivo com nome armazenado na cadeia de caracteres antigo tenha seu nome trocado pelo nome armazenado na cadeia de caracteres novo . Se a funo realiza a tarefa de troca de nome, ento rename devolve o valor 0 (zero). Caso contrrio, um valor diferente de 0 (zero) devolvido e o arquivo ainda pode ser identicado por seu nome antigo.
DCT
UFMS
56.3 F UNES
391
scanf("%d", &numero);
so equivalentes e lem um nmero do tipo inteiro da entrada padro, que normalmente o terminal. Do mesmo modo, o apontador stdout se refere sada padro, que tambm associada ao terminal. Assim, as chamadas a seguir:
printf("Programar bacana!\n");
so equivalentes e imprimem a mensagem acima entre as aspas duplas na sada padro, que normalmente o terminal. O apontador stderr se refere ao arquivo padro de erro, onde muitas das mensagens de erro produzidas pelo sistema so armazenadas e tambm normalmente associado ao terminal. Uma justicativa para existncia de tal arquivo , por exemplo, quando as sadas todas do programa so direcionadas para um arquivo. Assim, as sadas do programa so escritas em um arquivo e as mensagens de erro so escritas na sada padro, isto , no terminal. Ainda h a possibilidade de escrever nossas prprias mensagens de erro no arquivo apontado por stderr .
DCT UFMS
56.4 E XEMPLOS
392
56.4 Exemplos
Nessa seo apresentaremos dois exemplos que usam algumas das funes de entrada e sada em arquivos que aprendemos nesta aula. O primeiro exemplo, apresentando no programa 56.2, bem simples e realiza a cpia do contedo de um arquivo em outro arquivo. O segundo exemplo, apresentado no programa 56.3, mescla o contedo de dois arquivos texto em um arquivo resultante. Neste exemplo, cada par de palavras do arquivo resultante composto por uma palavra de um arquivo e uma palavra do outro arquivo. Se um dos arquivos contiver menos palavras que o outro arquivo, esse outro arquivo descarregado no arquivo resultante.
DCT
UFMS
56.4 E XEMPLOS
393
#include <stdio.h> #define MAX 100 int main(void) { char nomebase[MAX+1], nomecopia[MAX+1]; int c; FILE *aptbase, *aptcopia; printf("Informe o nome do arquivo a ser copiado: "); scanf("%s", nomebase); printf("Informe o nome do arquivo resultante: "); scanf("%s", nomecopia); aptbase = fopen(nomebase, "r"); if (aptbase != NULL) { aptcopia = fopen(nomecopia, "w"); if (aptcopia != NULL) { c = fgetc(aptbase); while (c != EOF) { fputc(c, aptcopia); c = fgetc(aptbase); } fclose(aptbase); fclose(aptcopia); printf("Arquivo copiado.\n"); } else printf("No possvel abrir o arquivo %s para escrita.\n", nomecopia); } else printf("No possvel abrir o arquivo %s para leitura.\n", nomebase); return 0; }
DCT
UFMS
56.4 E XEMPLOS
394
#include <stdio.h> #define MAX 100 int main(void) { char nomeum[MAX+1], nomedois[MAX+1], palavra1[MAX+1], palavra2[MAX+1]; int i1, i2; FILE *aptum, *aptdois, *aptresult; printf("Informe o nome do primeiro arquivo: "); scanf("%s", nomeum); printf("Informe o nome do segundo arquivo: "); scanf("%s", nomedois); aptum = fopen(nomeum, "r"); aptdois = fopen(nomedois, "r"); if (aptum != NULL && aptdois != NULL) { aptresult = fopen("mesclado", "w"); if (aptresult != NULL) { i1 = fscanf(aptum, "%s", palavra1); i2 = fscanf(aptdois, "%s", palavra2); while (i1 != EOF && i2 != EOF) { fprintf(aptresult, "%s %s ", palavra1, palavra2); i1 = fscanf(aptum, "%s", palavra1); i2 = fscanf(aptdois, "%s", palavra2); } while (i1 != EOF) { fprintf(aptresult, "%s ", palavra1); i1 = fscanf(aptum, "%s", palavra1); } while (i2 != EOF) { fprintf(aptresult, "%s ", palavra2); i2 = fscanf(aptdois, "%s", palavra2); } fprintf(aptresult, "\n"); fclose(aptum); fclose(aptdois); fclose(aptresult); printf("Arquivo mesclado.\n"); } else printf("No possvel abrir um arquivo para escrita.\n"); } else printf("No possvel abrir os arquivos para leitura.\n"); return 0; }
DCT
UFMS
56.4 E XEMPLOS
395
Exerccios
56.1 Escreva um programa que leia o contedo de um arquivo cujo nome fornecido pelo usurio e copie seu contedo em um outro arquivo, trocando todas as letras minsculas por letras maisculas. 56.2 Suponha que temos dois arquivos cujas linhas so ordenadas lexicogracamente. Por exemplo, esses arquivos podem conter nomes de pessoas, linha a linha, em ordem alfabtica. Escreva um programa que leia o contedo desses dois arquivos, cujos nomes so fornecidos pelo usurio, e crie um novo arquivo resultante contendo todas as linhas dos dois arquivos ordenadas lexicogracamente. Por exemplo, se os arquivos contm as linhas Arquivo 1 Antnio Berenice Diana Solange Snia Zuleica o arquivo resultante deve ser Antnio Berenice Carlos Clia Diana Fbio Henrique Solange Snia Zuleica Arquivo 2 Carlos Clia Fbio Henrique
DCT
UFMS
A ULA 57
L ISTAS
LINEARES
Listas lineares so as primeiras estruturas de dados que aprendemos. Essas estruturas so muito usadas em diversas aplicaes importantes para organizao de informaes na memria tais como representaes alternativas para expresses aritmticas, compartilhamento de espao de memria, entre outras. Nesta aula, aprenderemos a denio dessa estrutura, sua implementao em alocao esttica e suas operaes bsicas.
57.1 Denio
Informalmente, uma lista linear uma estrutura de dados que armazena um conjunto de informaes que so relacionadas entre si. Essa relao se expressa apenas pela ordem relativa entre os elementos. Por exemplo, nomes e telefones de uma agenda telefnica, as informaes bancrias dos funcionrios de uma empresa, etc, so informaes que podem ser armazenadas em uma lista linear. Cada informao contida na lista na verdade um registro contendo os dados relacionados. Em geral, usamos um desses dados como uma chave para realizar diversas operaes sobre essa lista, tais como busca de um elemento, insero, remoo, etc. J que os dados acoplados chave, tambm chamados de dados satlites, so irrelevantes e participam apenas das movimentaes dos registros, podemos imaginar que uma lista linear composta apenas pelas chaves desses registros e que essas chaves so representadas por nmeros inteiros. Agora formalmente, uma lista linear L um conjunto de n minada pela ordem relativa desses elementos: (i) se n > 0 ento l1 o primeiro registro; (ii) o registro li precedido pelo registro li1 para todo i, 1 < i As operaes bsicas sobre uma lista linear so as seguintes: busca; incluso; e remoo. De acordo com a poltica de armazenamento das informaes nos registros da lista, temos listas lineares especiais tais como pilhas e las, como veremos adiante. n. 0 registros l1 , l2 , . . . , ln deter-
396
57.1 D EFINIO
397
Dependendo da aplicao, muitas outras operaes tambm podem ser realizadas sobre essa estrutura como, por exemplo, alterao de um elemento, combinao de duas listas, ordenao da lista de acordo com as chaves, determinao do elemento de menor ou maior chave, determinao do tamanho ou nmero de elementos da lista, etc. As listas lineares podem ser armazenadas na memria de duas maneiras distintas: Alocao esttica ou seqencial: os elementos so armazenados em posies consecutivas de memria, com uso da vetores; Alocao dinmica ou encadeada: os elementos podem ser armazenados em posies no consecutivas de memria, com uso de apontadores. A aplicao, ou problema que queremos resolver, que dene o tipo de armazenamento a ser usado, dependendo das operaes sobre a lista, do nmero de listas envolvidas e das caractersticas particulares das listas. Nas aulas 41 e 32 vimos especialmente a operao de busca em uma lista linear em alocao seqencial. Os registros de uma lista linear em alocao encadeada, tambm chamados de clulas, encontram-se dispostos em posies aleatrias da memria e so ligados por apontadores que indicam a posio do prximo elemento da lista. Assim, um campo acrescentado a cada registro da lista indicando o endereo do prximo elemento da lista. Veja a gura 57.1 para um exemplo de um registro de uma lista.
conteudo prox
dados acoplados
Figura 57.1: Representao de um registro de uma lista linear em alocao encadeada. Os dados acoplados, ou dados satlites, so irrelevantes nas nossas aplicaes e por isso no so considerados. A denio de um registro de nossa lista linear ento descrita como a seguir:
conveniente tratar as clulas de uma lista linear em alocao encadeada como um tipo novo de dados, que chamaremos de celula :
DCT
UFMS
57.1 D EFINIO
398
Uma clula c e um apontador p para uma clula podem ser declarados da seguinte forma:
Se c uma clula ento c.conteudo o contedo da clula e c.prox o endereo da clula seguinte. Se p o endereo de uma clula ento p->conteudo o contedo da clula apontada por p e p->prox o endereo da clula seguinte. Se p o endereo da ltima clula da lista ento p->prox vale NULL . Uma ilustrao de uma lista linear em alocao encadeada mostrada na gura 57.2.
l5 l6 l3 l4 l2 l1
Figura 57.2: Representao de uma lista linear em alocao encadeada na memria. Dizemos que o endereo de uma lista encadeada o endereo de sua primeira clula. Se p o endereo de uma lista, podemos dizer simplesmente p uma lista e considere a lista p . Reciprocamente, a expresso p uma lista deve ser interpretada como p o endereo da primeira clula de uma lista. Uma lista linear pode ser vista de duas maneiras diferentes, dependendo do papel que sua primeira clula representa. Em uma lista linear com cabea, a primeira clula serve apenas para marcar o incio da lista e portanto o seu contedo irrelevante. A primeira clula a cabea da lista. Em uma lista linear sem cabea o contedo da primeira clula to relevante quanto o das demais. Nas nossas aulas, trataremos das listas lineares sem cabea, embora as listas lineares com cabea sejam ligeiramente mais simples de manipular. Assim, sempre que nos referirmos a uma lista linear, na verdade estaremos nos referindo implicitamente a uma lista linear sem cabea. Deixaremos as listas lineares com cabeas para os exerccios. Uma lista linear est vazia se no tem clula alguma. Para criar uma lista vazia lista basta escrever as seguintes sentenas:
DCT
UFMS
57.2 B USCA
399
Para imprimir o contedo de todas as clulas de uma lista linear lista podemos usar a seguinte funo:
void imprime_lista(celula *lista) { celula *p; for (p = lista; p != NULL; p = p->prox) printf("%d\n", p->conteudo); }
A seguir vamos discutir e implementar as operaes bsicas de busca, insero e remoo sobre listas lineares em alocao encadeada.
57.2 Busca
O processo de busca de um objeto x em uma lista linear em alocao encadeada, isto , de vericar se x igual ao contedo de alguma clula da lista, pode ser descrito como apresentamos abaixo. A funo buscaEnc recebe uma lista linear apontada por lista e um nmero inteiro x e devolve o endereo de um registro contendo x , caso x ocorra em lista . Caso contrrio, a funo devolve NULL .
1 2 3 4 5 6 7 8
celula *buscaEnc(celula *lista, int x) { celula *p; p = lista; while (p != NULL && p->conteudo != x) p = p->prox; return p; }
interessante comparar a ecincia dessa funo com a da funo busca da aula 32, que realiza processamento equivalente, porm em uma lista linear em alocao seqencial, isto , em um vetor. Uma chamada funo buscaEnc pode ser realizada como mostramos a seguir, supondo apontadores p e lista do tipo celula e x um nmero inteiro:
p = buscaEnc(lista, x);
DCT
UFMS
57.3 I NSERO
400
57.3 Insero
Observe que na alocao encadeada, os ns de uma lista linear se encontram dispostos em posies de memria no necessariamente contnuas. Com a realizao de inseres e remoes, h necessidade de encontrar novas posies de memria para armazenamento de informaes e liberar outras posies que possam ser reutilizadas posteriormente. Podemos gerenciar a memria na linguagem C usando funes que gerenciam seu espao disponvel. As funes malloc e free , da biblioteca stdlib , executam a tarefa de ocupao e liberao, respectivamente, de compartimentos da lista de espaos disponveis da memria, como vimos na aula 55. Uma funo que insere um elemento x na lista linear L apresentada a seguir. Se x j consta da lista, ento nada fazemos. Caso contrrio, inserimos x no incio da lista.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
void insereEnc(celula **L, int x) { celula *novo; if (buscaEnc(*L, x) == NULL) { novo = (celula *) malloc(sizeof(celula)); if (novo != NULL) { novo->conteudo = x; novo->prox = *L; *L = novo; } else { printf("No h memria disponvel!\n"); exit(EXIT_FAILURE); } } else printf("Valor j se encontra na lista!\n"); }
Uma chamada funo insereEnc pode ser feita como abaixo, para uma lista e um valor x :
insereEnc(&lista, x);
Observe ainda que as inseres so sempre realizadas no incio da lista linear. Podemos modicar essa comportamento, por exemplo, mantendo um outro apontador para o nal da lista linear e fazendo as inseres neste outro extremo. Deixamos essa idia para ser implementada em um exerccio.
DCT
UFMS
57.4 R EMOO
401
57.4 Remoo
Para realizar a remoo de um registro da lista necessrio busc-lo. A funo buscaEnc descrita nesta aula devolve um apontador para o registro cujo contedo procurado se encontra, ou NULL caso esse valor no ocorra na lista. Um exemplo desta situao apresentado na gura 57.3.
p
Figura 57.3: Busca em uma lista linear encadeada. O apontador devolvido como resultado. Depois de ter encontrado o registro que contm o elemento que queremos remover, o que devemos fazer? Infelizmente, esta funo buscaEnc no devolve uma informao fundamental para que a remoo possa ser realizada imediatamente com sucesso: um apontador para o registro anterior ao registro que contm o valor que queremos remover. No exemplo da gura 57.3, precisamos de um apontador para o registro que contm o valor 6. Como s temos um apontador para o registro seguinte, no conseguimos realizar a remoo, a menos que faamos algum truque. Mesmo assim, ainda possvel realizar a remoo, mas vamos deixar idias mais complicadas para um exerccio. A idia a ser apresentada aqui modicar um pouco a funo buscaEnc de modo que possa devolver dois apontadores: um para o registro que contm o valor buscado e outro para o registro imediatamente anterior a esse. A funo buscaEnc2 a seguir resultado dessa idia.
1 2 3 4 5 6 7 8 9
void buscaEnc2(celula *L, celula **apt, celula **ant, int x) { *ant = NULL; *apt = L; while (*apt != NULL && (*apt)->conteudo != x) { *ant = *apt; *apt = (*apt)->prox; } }
Observe que a funo buscaEnc2 devolve um apontador para o elemento procurado em *apt e um apontador para o elemento anterior ao procurado em *ant , se o elemento de fato se encontra na lista. Caso contrrio, *apt conter um apontador NULL , como antes. Agora, com os apontadores *apt e *ant , podemos descrever a funo removeEnc .
DCT
UFMS
57.4 R EMOO
402
1 2 3 4 5 6 7 8 9 10 11 12 13 14
void removeEnc(celula **L, int x) { celula *apt, *ant; buscaEnc2(*L, &apt, &ant, x); if (apt != NULL) { if (ant != NULL) ant->prox = apt->prox; else *L = apt->prox; free(p); } else printf("Valor no encontrado\n"); }
(i)
ant p
(ii)
ant p
(iii) Figura 57.4: Remoo do valor 5 em uma lista linear encadeada. (i) Aps a busca do valor. (ii) Modicao do apontador ant . (iii) Liberao da memria.
DCT UFMS
57.4 R EMOO
403
Uma chamada funo removeEnc ilustrada abaixo, para uma lista e um valor x :
removeEnc(&lista, x);
Observe que os apontadores apt e ant so variveis locais funo removeEnc , que auxiliam tanto na busca como na remoo propriamente. Alm disso, se ant == NULL ento o registro que contm x , que queremos remover, encontra-se na primeira posio da lista. Dessa forma, h necessidade de alterar o contedo do apontador *L para o registro seguinte desta lista.
Exerccios
57.1 Escreva uma funo insereFim que receba uma lista linear *L , um apontador f para o m da lista e um valor x e realize a insero desse valor no nal da lista. 57.2 Se conhecemos apenas o apontador apt para um n de uma lista linear em alocao encadeada, como na gura 57.5, e nada mais conhecido, como podemos modicar a lista linear de modo que passe a conter apenas os valores 20, 4, 19, 47, isto , sem o contedo do n apontado por apt ?
apt
20
19
47
Figura 57.5: Uma lista linear encadeada com um apontador apt . 57.3 O esquema apresentado na gura 57.6 permite percorrer uma lista linear encadeada nos dois sentidos, usando apenas o campo prox que contm o endereo do prximo elemento da lista. Usamos dois apontadores esq e dir , que apontam para dois elementos vizinhos da lista. A idia desse esquema que medida que os apontadores esq e dir caminham na lista, os campos prox so invertidos de maneira a permitir o trfego nos dois sentidos. Escreva funes para: (a) mover esq e dir para a direita de uma posio. (b) mover esq e dir para a esquerda de uma posio.
DCT
UFMS
57.4 R EMOO
404
esq
dir
(i)
esq dir
(ii) Figura 57.6: A gura (ii) foi obtida de (i) atravs da execuo da funo dada no exerccio 57.4(a) por trs vezes. 57.4 Uma lista linear encadeada com cabea tal que a primeira clula serve apenas para marcar o incio da lista e portanto o seu contedo irrelevante. A primeira clula a cabea da lista. Se L o endereo da cabea de lista ento L->prox vale NULL se e somente se a lista est vazia. Para criar uma lista vazia deste tipo, devemos fazer o seguinte:
Escreva funes de busca, insero e remoo em uma lista linear com cabea. 57.5 (a) Escreva uma funo que copie um vetor para uma lista linear encadeada. (b) Escreva uma funo que copie uma lista linear encadeada em um vetor. 57.6 Escreva uma funo que decida se duas listas dadas tm o mesmo contedo. 57.7 Escreva uma funo que conte o nmero de clulas de uma lista linear encadeada. 57.8 Seja lista uma lista linear com seus contedos dispostos em ordem crescente. Escreva as funes buscaEncOrd , insereEncOrd e removeEncOrd para realizao da busca, insero e remoo, respectivamente, em uma lista linear com essas caracterstica. As operaes de insero e remoo devem manter a lista em ordem crescente. 57.9 Sejam duas listas lineares L1 e L2 , com seus contedos dispostos em ordem crescente. Escreva uma funo concatena que receba L1 e L2 e construa uma lista R resulDCT UFMS
57.4 R EMOO
405
tante da intercalao dessas duas listas, de tal forma que a lista construda tambm esteja ordenada. A funo concatena deve destruir as listas L1 e L2 e deve devolver R . 57.10 Seja L uma lista linear composta por registros contendo os valores l1 , l2 , . . . ln , nessa ordem. (a) Escreva uma funo roda1 que receba *L e modique e devolva essa lista de tal forma que a lista resultante contenha as chaves l2 , l3 , . . . , ln , l1 , nessa ordem. (b) Escreva uma funo inverte que receba *L e modique e devolva essa lista de tal forma que a lista resultante contenha as chaves ln , ln1 , . . . , l2 , l1 , nessa ordem. (c) Escreva uma funo soma que receba *L e modique e devolva essa lista de tal forma que a lista resultante contenha as chaves l1 + ln , l2 + ln1 , . . . , ln/2 + ln/2+1 , nessa ordem. Considere n par. 57.11 Sejam S1 e S2 dois conjuntos disjuntos de nmeros inteiros. Suponha que S1 e S2 esto implementados em duas listas lineares em alocao encadeada L1 e L2 , respectivamente. Escreva uma funo uniao que receba as listas L1 e L2 representando os conjuntos S1 e S2 e devolva uma lista resultante R que representa a unio dos conjuntos, isto , uma lista linear encadeada que representa o conjunto S = S1 S2 . 57.12 Seja um polinmio p(x) = a0 xn + a1 xn1 + . . . + an , com coecientes de ponto utuante. Represente p(x) adequadamente por uma lista linear encadeada e escreva as seguintes funes. (a) Escreva uma funo pponto que receba uma lista p e um nmero de ponto utuante x0 e calcule e devolva p(x0 ). (b) Escreva uma funo psoma que receba as listas lineares p e q , que representam dois polinmios, e calcule e devolva o polinmio resultante da operao p(x) + q (x). (c) Escreva uma funo pprod que receba as listas lineares p e q , que representam dois polinmios, e calcule e devolva o polinmio resultante da operao p(x) q (x).
DCT
UFMS
A ULA 58
P ILHAS
O armazenamento seqencial de uma lista usado, em geral, quando essas estruturas sofrem poucas modicaes ao longo do tempo, isto , poucas inseres e remoes so realizadas durante sua existncia. Isso implica em poucas movimentaes de registros. Por outro lado, a alocao encadeada mais usada na situao inversa, ou seja, quando modicaes na lista linear so mais freqentes. Caso os elementos a serem inseridos ou removidos de uma lista linear se localizem em posies especiais nessas estruturas, como por exemplo a primeira ou a ltima posio, ento temos uma situao favorvel para o uso de listas lineares em alocao seqencial. Este o caso da estrutura de dados denominada pilha. O que torna essa lista linear especial a adoo de uma poltica bem denida de inseres e remoes, sempre em um dos extremos da lista. A implementao de uma pilha em alocao encadeada tem muitas aplicaes importantes e tambm ser discutida nesta aula.
58.1 Denio
Uma pilha uma lista linear tal que as operaes de insero e remoo so realizadas em um nico extremo dessa estrutura de dados. O funcionamento dessa lista linear pode ser comparado a qualquer pilha de objetos que usamos com freqncia como, por exemplo, uma pilha de pratos de um restaurante. Em geral, os clientes do restaurante retiram pratos do topo da pilha e os funcionrios colocam pratos limpos tambm no topo. Observe que uma pilha tem sempre um indicador do extremo em que uma insero ou remoo deve ser realizada. O indicador desse extremo, nesta estrutura, chamado de topo da pilha. Duas operaes bsicas so realizadas sobre uma pilha: insero e remoo. Essas duas operaes so tambm chamadas de empilhamento e desempilhamento de elementos. Observe que a operao de busca no foi mencionada e no faz parte do conjunto de operaes bsicas de uma pilha.
58.2 O PERAES
407
Figura 58.1: Representao de uma pilha P em alocao seqencial. Convencionamos que uma pilha est vazia se seu topo t vale 1 e cheia se seu topo t vale MAX-1 . Suponha que queremos inserir um elemento de valor 7 na pilha P da gura 58.1. Como resultado desta operao, esperamos que a pilha tenha a congurao mostrada na gura 58.2 aps sua execuo.
0 P 3 1 1 2 8 3 7 MAX-1
Figura 58.2: Insero da chave 7 na pilha P da gura 58.1. A operao de inserir um objeto em uma pilha, ou empilhar, descrita a seguir.
1 2 3 4 5 6 7 8 9
void empilhaSeq(int P[MAX], int *t, int x) { if (*t != MAX-1) { (*t)++; P[*t] = x; } else printf("Impossvel inserir um novo elemento na pilha\n"); }
Suponha agora que queremos remover um elemento de uma pilha. Observe que, assim como na insero, a remoo tambm deve ser feita em um dos extremos da pilha, isto , no topo da pilha. Assim, a remoo de um elemento da pilha P que tem sua congurao ilustrada na gura 58.1 tem como resultado a pilha com a congurao mostrada na gura 58.3 aps a sua execuo. A operao de remover, ou desempilhar, um objeto de uma pilha descrita na funo desempilhaSeq a seguir.
DCT
UFMS
58.2 O PERAES
408
0 P 3
1 1
MAX-1
1 2 3 4 5 6 7 8 9 10 11 12 13
int desempilhaSeq(int P[MAX], int *t) { int removido; if (*t != -1) { removido = P[*t]; (*t)--; return removido; } else { printf("Impossvel remover um elemento da pilha\n"); return INT_MIN; } }
Agora que temos implementadas as operaes bsicas de insero e remoo, ou de empilhar e desempilhar, em uma pilha, podemos usar essas funes em aplicaes que necessitam dessa estrutura de dados. Diversas so as aplicaes que necessitam de uma ou mais pilhas, dentre as quais podemos citar a converso de notao de expresses aritmticas (notao inxa para notao posxa), a pilha de execuo de um programa no sistema operacional, a anlise lxica de um programa realizada pelo compilador, etc. Uma aplicao simples e interessante apresentada a seguir. Considere, por exemplo, o problema de decidir se uma dada seqncia de parnteses e chaves bem-formada. Por exemplo, a seqncia abaixo:
( ( ) { ( ) } )
malformada. Suponha que a seqncia de parnteses e chaves est armazenada em uma cadeia de caracteres s . A funo bemformada a seguir recebe a cadeia de caracteres s e devolve 1 se s contm uma seqncia bem-formada de parnteses e chaves e devolve 0 se a seqncia est malformada.
DCT
UFMS
58.3 O PERAES
409
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
int bemformada(char s[]) { char *p; int t; int n, i; n = strlen(s); p = (char *) malloc(n * sizeof(char)); t = -1; for (i = 0; s[i] != \0; i++) { switch (s[i]) { case ): if (t != -1 && p[t] == () t--; else return 0; break; case }: if (t != -1 && p[t] == {) t--; else return 0; break; default: t++; p[t] = s[i]; } } free(p); return t == -1; }
DCT
UFMS
58.3 O PERAES
410
A operao de empilhar uma nova chave x em uma pilha t descrita na funo a seguir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
void empilhaEnc(celula **t, int x) { celula *nova; nova = (celula *) malloc(sizeof(celula)); if (nova != NULL) { nova->conteudo = x; nova->prox = *t; *t = nova; } else { printf("Impossvel inserir um novo elemento na pilha\n"); exit(EXIT_FAILURE); } }
Suponha agora que queremos remover um elemento do topo de uma pilha t . A operao de desempilhar um registro descrita na funo a seguir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
int desempilhaEnc(celula **t) { int x; celula *aux; if (*t != NULL) { aux = *t; x = (*t)->conteudo; *t = (*t)->prox; free(aux); return x; } else { printf("Impossvel remover um elemento da pilha\n"); return INT_MIN; } }
DCT
UFMS
58.3 O PERAES
411
Exerccios
58.1 Reescreva a funo bemformada armazenando a pilha em uma lista linear em alocao encadeada. 58.2 Uma palavra um palndromo se a seqncia de caracteres que a constitui a mesma quer seja lida da esquerda para a direita ou da direita para a esquerda. Por exemplo, as palavras RADAR e MIRIM so palndromos. Escreva um programa eciente para reconhecer se uma dada palavra palndromo. 58.3 Suponha que exista um nico vetor M de registros de um tipo pilha pr-denido, com um total de MAX posies. Este vetor far o papel da memria do computador. Este vetor M ser compartilhado por duas pilhas em alocao seqencial. Implemente ecientemente as operaes de empilhamento e desempilhamento para as duas pilhas de modo que nenhuma das pilhas estoure sua capacidade de armazenamento, a menos que o total de elementos em ambas as pilhas seja MAX. 58.4 Um estacionamento possui um nico corredor que permite dispor 10 carros. Existe somente uma nica entrada/sada do estacionamento em um dos extremos do corredor. Se um cliente quer retirar um carro que no est prximo sada, todos os carros impedindo sua passagem so retirados, o cliente retira seu carro e os outros carros so recolocados na mesma ordem que estavam originalmente. Escreva um algoritmo que processa o uxo de chegada/sada deste estacionamento. Cada entrada para o algoritmo contm uma letra E para entrada ou S para sada, e o nmero da placa do carro. Considere que os carros chegam e saem pela ordem especicada na entrada. O algoritmo deve imprimir uma mensagem sempre que um carro chega ou sai. Quando um carro chega, a mensagem deve especicar se existe ou no vaga para o carro no estacionamento. Se no existe vaga, o carro no entra no estacionamento e vai embora. Quando um carro sai do estacionamento, a mensagem deve incluir o nmero de vezes que o carro foi movimentado para fora da garagem, para permitir que outros carros pudessem sair.
DCT
UFMS
A ULA 59
F ILAS
Uma la , assim como uma pilha, uma lista linear especial, onde h uma poltica de inseres e remoes bem denida. Em uma la, uma insero realizada em um dos extremos da la e a remoo no outro extremo. Em uma pilha, como vimos na aula passada, a insero e a remoo so realizadas em um mesmo extremo. Nesta aula veremos como implementar as operaes bsicas sobre uma la, considerando sua implementao em alocao seqencial e encadeada.
59.1 Denio
Uma la uma lista linear tal que as operaes de insero so realizadas em um dos extremos da lista e a remoo realizada no outro extremo. Uma ilustrao de uma la mostrada na gura 59.1. O funcionamento dessa estrutura pode ser comparado a qualquer la de objetos que usamos com freqncia como, por exemplo, uma la de um banco. Em geral, as pessoas entram, ou so inseridas, no nal da la e as pessoas saem, ou so removidas, do incio da la.
0 F 3 1 1 2 8 3 MAX-1
Figura 59.1: Representao de uma la F em alocao seqencial. Note que uma la tem sempre um indicador de onde comea e de onde termina. Quando queremos inserir um elemento na la, esta operao realizada no nal da estrutura. Quando queremos remover um elemento da la, essa operao realizada no incio da estrutura. Esses extremos so referenciados como inicio e fim de uma la F . Se a la F implementada em alocao seqencial, isto , sobre um vetor, ento esses extremos so ndices desse vetor. Caso contrrio, se for implementada em alocao encadeada, ento esses extremos so apontadores para o primeiro e o ltimo registros da la. Assim como nas pilhas, duas operaes bsicas so realizadas sobre uma la: insero e remoo. Essas duas operaes so tambm chamadas de enleiramento e desenleiramento de elementos. Observe que a operao de busca no foi mencionada e no faz parte do conjunto de operaes bsicas de uma la. 412
59.2 O PERAES
413
Suponha que queremos inserir um elemento de chave 7 na la F da gura 59.1. Como resultado desta operao, esperamos que a la tenha a congurao mostrada na gura 59.2 aps sua execuo.
0 F 3 1 1 2 8 3 7 MAX-1
Figura 59.2: Insero da chave 7 na la F da gura 59.1. Observe que o ndice i move-se somente quando da remoo de um registro da la e o ndice f move-se somente quando da insero de um registro na la. Esses incrementos fazem com que a la movimente-se da esquerda para direita, o que pode ocasionar a falsa impresso de transbordamento de memria. Consideramos ento os registros alocados seqencialmente como se estivessem em um crculo, onde o compartimento F[MAX-1] seguido pelo compartimento F[0] . A operao de enleirar um novo nmero inteiro x em uma la F descrita a seguir.
1 2 3 4 5 6 7 8 9 10 11 12 13
void enfileiraSeq(int F[], int *i, int *f, int x) { int aux; aux = (*f + 1) % MAX; if (aux != *i) { *f = aux; F[*f] = x; if (*i == -1) *i = 0; } else printf("Impossvel inserir um novo elemento na fila\n"); }
A varivel aux um ndice temporrio para o nal da la. Se o ndice armazenado em aux igual ao ndice *i , ento no h mais espao disponvel na la. Caso contrrio, o ndice *f atualizado com esse valor e o novo elemento colocado no nal da la. Suponha agora que queremos remover um elemento da la F que aparece na gura 59.1. Como resultado desta operao, esperamos que a la tenha a congurao mostrada na gura 59.3 aps sua execuo.
DCT UFMS
59.2 O PERAES
414
0 F
1 1
2 8
MAX-1
Figura 59.3: Remoo de um elemento da la F da gura 59.1. A operao de desenleirar um nmero inteiro em uma la F descrita a seguir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
int desenfileiraSeq(int F[], int *i, int *f) { int removido; removido = INT_MIN; if (*i != -1) { removido = F[*i]; if (*i != *f) *i = (*i + 1) % MAX; else { *i = -1; *f = -1; } } else printf("Impossvel remover um elemento da fila\n"); return removido; }
Como um exemplo de aplicao, suponha que temos n cidades numeradas de 0 a n 1 e interligadas por estradas de mo nica. As ligaes entre as cidades so representadas por uma matriz A denida da seguinte forma: A[x][y] vale 1 se existe estrada da cidade x para a cidade y e vale 0 em caso contrrio. A gura 59.4 ilustra um exemplo. A distncia de uma cidade o a uma cidade x o menor nmero de estradas que preciso percorrer para ir de o a x . Estabelecemos ento o seguinte problema: determinar a distncia de uma dada cidade o a cada uma das outras cidades da rede. As distncias so armazenadas em um vetor d de tal modo que d[x] seja a distncia de o a x . Se for impossvel chegar de o a x , podemos dizer que d[x] vale . Usaremos ainda 1 para representar uma vez que nenhuma distncia real pode ter valor 1.
O programa 59.1 usa uma la para solucionar o problema das distncias em uma rede. O programa classica as cidades durante sua execuo, da seguinte forma: uma cidade considerada ativa se j foi visitada mas as estradas que nela comeam ainda no forma exploradas. As cidades ativas so mantidas numa la pelo programa. Em cada iterao, o programa remove da la uma cidade x e insere na la todas as cidades vizinhas a x que ainda no foram visitadas.
DCT
UFMS
59.3 O PERAES
415
0 1 2 3 4 5
0 0 0 0 0 1 0
1 1 0 0 0 0 1
2 0 1 0 1 0 0
3 0 0 0 0 0 0
4 0 0 1 1 0 0
5 0 0 0 0 0 0
4 0 1 2 3 4 5 d 2 3 1 0 1 6
Figura 59.4: A matriz representa cidades 0, . . . , 5 interligadas por estradas de mo nica. O vetor d d as distncias da cidade 3 a cada uma das demais. Programa 59.1: Exemplo de uma funo que usa uma la.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
int *distancias(int A[MAX][MAX], int n, int o) { int *d, x, y; int *F, i, f; d = (int *) malloc(n * sizeof(int)); for (x = 0; x < n; x++) d[x] = -1; d[o] = 0; F = (int *) malloc(n * sizeof(int)); i = 0; f = 0; F[i] = o; while (i <= f) { x = F[i]; i++; for (y = 0; y < n; y++) if (A[x][y] == 1 && d[y] == -1) { d[y] = d[x] + 1; f++; F[f] = y; } } free(F); return d; }
59.3 O PERAES
416
nas clulas de uma la estabelece que quando queremos inserir um elemento em uma la, essa operao sempre realizada no extremo identicado como o nal dessa estrutura. Por outro lado, quando queremos remover um elemento da la, essa operao sempre realizada no extremo identicado como incio da estrutura. Em alocao encadeada, esses extremos so referenciados atravs de apontadores para clulas da la, que contm o endereo das suas primeira e ltima clulas. Assim como nas pilhas, apenas duas operaes bsicas so realizadas sobre as las: insero e remoo. Essas duas operaes so tambm chamadas de enleiramento e deseleiramento de elementos. Observe que a operao bsica de busca, que especicamos nas listas lineares, conforme a aula 57, no foi mencionada e tambm no faz parte do conjunto de operaes bsicas de uma la. Uma la em alocao encadeada dita vazia se e somente se ambos seus apontadores de incio e m contm o valor nulo ( NULL ). Dessa forma, podemos criar facilmente uma la vazia como especicado abaixo:
A operao de enleirar um novo elemento x em uma la dada atravs da funo descrita abaixo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
void enfileiraEnc(celula **comeco, celula **final, int x) { celula *nova; nova = (celula *) malloc(sizeof(celula)); if (nova != NULL) { nova->conteudo = x; nova->prox = NULL; if (*final != NULL) (*final)->prox = nova; else *comeco = nova; *final = nova; } else printf("Impossvel inserir um novo elemento na fila\n"); }
Suponha agora que queremos desenleirar um elemento de uma la identicada pelos apontadores *comeco e *final . A operao de desenleirar uma clula dessa la descrita a seguir.
DCT
UFMS
59.3 O PERAES
417
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
int desenfileiraEnc(celula **comeco, celula **final) { int x; celula *p; if (*comeco != NULL) { p = *comeco; x = p->conteudo; *comeco = p->prox; free(p); if (*comeco == NULL) *final = NULL; return x; } else { printf("Impossvel remover um elemento da fila\n"); return INT_MIN; } }
Exerccios
59.1 Solucione o problema das distncias em uma rede usando uma la em alocao encadeada. 59.2 Implemente uma la usando duas pilhas. 59.3 Implemente uma pilha usando duas las. 59.4 Um estacionamento possui um nico corredor que permite dispor 10 carros. Os carros chegam pelo sul do estacionamento e saem pelo norte. Se um cliente quer retirar um carro que no est prximo do extremo norte, todos os carros impedindo sua passagem so retirados, o cliente retira seu carro e os outros carros so recolocados na mesma ordem que estavam originalmente. Sempre que um carro sai, todos os carros do sul so movidos para frente, de modo que as vagas quem disponveis sempre no extremo sul do estacionamento. Escreva um algoritmo que processa o uxo de chegada/sada deste estacionamento. Cada entrada para o algoritmo contm uma letra E para entrada ou S para sada, e o nmero da placa do carro. Considere que os carros chegam e saem pela ordem especicada na entrada. O algoritmo deve imprimir uma mensagem sempre que um carro chega ou sai. Quando um carro chega, a mensagem deve especicar se existe ou no vaga para o carro no estacionamento. Se no existe vaga, o carro deve esperar at que exista uma vaga, ou at que uma instruo fornecida pelo usurio indique que o carro deve partir sem que entre no estacionamento. Quando uma vaga torna-se disponvel, outra mensagem deve ser impressa. Quando um carro sai do estacionamento, a mensagem deve incluir o nmero de vezes que o carro foi movimentado dentro da garagem, incluindo a sada mas no a chegada. Este nmero 0 se o carro partiu da linha de espera, isto , se o carro esperava uma vaga, mas partiu sem entrar no estacionamento.
DCT UFMS
59.3 O PERAES
418
59.5 Imagine um tabuleiro quadrado 10por10. As casas livres so marcadas com 0 e as casas bloqueadas so marcadas com 1. As casas (1, 1) e (10, 10) esto livres. Ajude uma formiga que est na casa (1, 1) a chegar casa (10, 10). Em cada passo, a formiga s pode se deslocar para uma casa livre que esteja direita, esquerda, acima ou abaixo da casa em que est. 59.6 Um deque uma lista linear que permite a insero e a remoo de elementos em ambos os seus extremos. Escreva quatro funes para manipular um deque: uma que realiza a insero de um novo elemento no incio do deque, uma que realiza a insero de um novo elemento no m do deque, uma que realiza a remoo de um elemento no incio do deque e uma que realiza a remoo de um elemento no m do deque.
DCT
UFMS
A ULA 60
L ISTAS
LINEARES CIRCULARES
Algumas operaes bsicas em listas lineares, em especial aquelas implementadas em alocao encadeada, no so muito ecientes. Um exemplo emblemtico desse tipo de operao a busca, como pudemos perceber em aulas anteriores. As listas lineares circulares realizam algumas operaes mais ecientemente, j que possuem uma informao a mais que as listas lineares ordinrias. Uma lista linear circular , em geral, implementada em alocao encadeada e difere de uma lista linear por no conter um apontador para nulo em qualquer de seus registros, o que no permite que se identique um m, ou mesmo um comeo, da lista. Caso um registro especial, chamado cabea da lista, faa parte da sua estrutura, ento possvel identicar facilmente um incio e um m da lista linear. Nesta aula veremos a implementao das operaes bsicas de busca, insero e remoo em listas lineares circulares em alocao encadeada.
Figura 60.1: Representao de uma lista linear circular em alocao encadeada. A denio de um tipo clula da lista linear circular permanece a mesma, como mostramos abaixo: 419
60.2 B USCA
420
A seguir vamos discutir e implementar as operaes bsicas de busca, insero e remoo sobre listas lineares circulares em alocao encadeada. importante observar que a implementao dessas listas tornam-se mais fceis se usamos o recurso de inserir uma clula especial no incio da lista, chamada de cabea.
60.2 Busca
No processo de busca, dada uma lista linear circular e um valor, queremos saber se o valor est presente na lista. A funo buscaCirc recebe um apontador circ para a lista linear circular e um valor x e devolve uma clula contendo o valor procurado x , caso x ocorra em na lista. Caso contrrio, a funo devolve NULL .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
celula *buscaCirc(celula *circ, int x) { celula *p; if (circ != NULL) { p = circ->prox; while (p != circ) { if (p->conteudo == x) return p; p = p->prox; } if (p->conteudo == x) return p; } return NULL; }
60.3 Insero
A insero em uma lista linear circular encadeada simples e semelhante insero em uma lista linear encadeada. Temos apenas de prestar ateno para que a insero sempre mantenha a propriedade da lista linear ser circular, o que se d de modo natural se inserimos o novo elemento logo em seguida clula referenciada pelo apontador da lista.
DCT
UFMS
60.4 R EMOO
421
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
void insereCirc(celula **circ, int x) { celula *nova; if (buscaCirc(*circ, x) == NULL) { nova = (celula *) malloc(sizeof(celula)); nova->conteudo = x; if (*circ != NULL) { nova->prox = (*circ)->prox; (*circ)->prox = nova; } else { nova->prox = nova; *circ = nova; } } else printf("Valor j se encontra na lista!\n"); }
60.4 Remoo
A funo de remoo de um elemento em uma lista linear circular necessita de uma modicao na funo de busca, de modo a devolver implicitamente dois apontadores, um para a clula contendo o elemento procurado e outro para a clula imediatamente anterior. A funo buscaCirc2 uma modicao das funes buscaCirc e buscaSeq2 e deixada como exerccio.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
void removeCirc(celula **circ, int x) { celula *p, *ant; buscaCirc2(*circ, &p, &ant, x); if (p != NULL) { if (p != ant) { ant->prox = p->prox; if (p == *circ) *circ = p->prox; } else *circ = NULL; free(p); } else printf("Elemento no encontrado!\n"); }
DCT
UFMS
60.4 R EMOO
422
Exerccios
60.1 Escreva a funo buscaCirc2 , que receba uma lista linear circular e um valor x e devolva dois apontadores ant e p que apontam para a clula anterior a clula que contm x , respectivamente. 60.2 Suponha que queremos implementar uma lista linear circular em alocao encadeada de tal forma que os contedos sempre permaneam em ordem crescente. Escreva as funes para as operaes bsicas de busca, insero e remoo para listas lineares circulares em alocao encadeada com os contedos ordenados. 60.3 Tambm para listas lineares circulares vale que as funes que implementam suas operaes bsicas so mais simples e ecientes se mantemos uma clula que indica seu incio, chamada de cabea. Veja a gura 60.2. Escreva as funes para as operaes bsicas de busca, insero e remoo para listas lineares circulares em alocao encadeada com cabea.
p
(a)
p
(b) Figura 60.2: Lista linear circular em alocao encadeada com cabea. (a) Lista vazia. (b) Lista com trs clulas. 60.4 O problema de Josephus foi assim descrito atravs do relato de Flavius Josephus, um historiador judeu que viveu no primeiro sculo, sobre o cerco de Yodfat. Ele e mais 40 soldados aliados estavam encurralados em uma caverna rodeada por soldados romanos e, no havendo sada, optaram ento pelo suicdio antes da captura. Decidiram que formariam um crculo e, a cada contagem de 3, um soldado seria morto. Josephus foi o ltimo a se salvar.
DCT
UFMS
60.4 R EMOO
423
Podemos agora, descrever um problema mais geral como segue. Imagine n pessoas dispostas em crculo. Suponha que as pessoas so numeradas de 1 a n no sentido horrio. Comeando com a pessoa de nmero 1, percorra o crculo no sentido horrio e elimine cada m-sima pessoa enquanto o crculo tiver duas ou mais pessoas. Escreva e teste uma funo que resolva o problema, imprimindo na sada o nmero do sobrevivente.
DCT
UFMS
A ULA 61
L ISTAS
Quando trabalhamos com listas lineares (simplesmente) encadeadas, muitas vezes precisamos de manter um apontador para um registro e tambm para o registro anterior para poder realizar algumas operaes evitando percursos adicionais desnecessrios. No entanto, algumas vezes isso no suciente, j que podemos precisar percorrer a lista linear nos dois sentidos. Neste caso, alm do campo apontador para o prximo registro no registro base da lista, adicionamos um novo campo apontador para o registro anterior. O gasto de memria imposto pela adio deste novo campo se justica pela economia em no ter de reprocessar a lista linear inteira.
61.1 Denio
Os registros de uma lista linear duplamente encadeada so ligados por apontadores que indicam a posio do registro anterior e do prximo registro da lista. Assim, um outro campo acrescentado a cada registro da lista indicando, alm do endereo do prximo registro, o endereo do registro anterior da lista. Veja a gura 61.1.
conteudo
ant
prox
Figura 61.1: Representao de uma clula de uma lista linear duplamente encadeada. A denio de uma clula de uma lista linear duplamente encadeada ento descrita como um tipo, como mostramos a seguir:
typedef struct cel { int conteudo; struct cel *ant; struct cel *prox; } celula;
424
61.2 B USCA
425
Uma representao grca de uma lista linear em alocao encadeada mostrada na gura 61.2.
lista_d
l1
l2
ln
Figura 61.2: Representao de uma lista linear duplamente encadeada apontada por lista_dup. A primeira clula tem seu campo ant apontando para NULL e a ltima tem seu campo prox tambm apontando para NULL. Uma lista linear duplamente encadeada pode ser criada e inicializada de forma idntica a de uma lista linear, como mostramos a seguir:
A seguir vamos discutir e implementar as operaes bsicas sobre listas lineares duplamente encadeadas.
61.2 Busca
No processo de busca de um valor em uma lista linear duplamente encadeada, dada uma lista linear duplamente encadeada dup e um valor x e queremos saber se x est presente em dup . Uma funo que realiza esse processo pode ser descrita como a seguir. A funo buscaDup recebe um apontador dup para uma lista duplamente encadeada e um valor x e devolve um registro contendo o valor procurado x , caso x ocorra em dup . Caso contrrio, a funo devolve NULL .
1 2 3 4 5 6 7 8
celula *buscaDup(celula *dup, int x) { celula *p; p = dup; while (p != NULL && p->conteudo != x) p = p->prox; return p; }
61.3 I NSERO
426
61.3 Insero
A funo que insere um elemento x em uma lista linear duplamente encadeada *dup apresentada a seguir. A insero continua sendo feita no incio da lista.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
void insereDup(celula **dup, int x) { celula *nova; if (buscaDup(*dup, x) == NULL) { nova = (celula *) malloc(sizeof(celula)); if (nova != NULL) { nova->conteudo = x; nova->ant = NULL; nova->prox = *dup; if (*dup != NULL) (*dup)->ant = nova; *dup = nova; } else { printf("No h memria disponvel!\n"); exit(EXIT_FAILURE); } } else printf("Elemento j se encontra na lista!\n"); }
Observe novamente que as inseres so sempre realizadas no incio da lista linear duplamente encadeada. Podemos modicar essa comportamento, por exemplo, mantendo um outro apontador para o nal da lista e fazendo as inseres neste outro extremo. Deixamos essa idia para ser implementada em um exerccio.
61.4 Remoo
Como sabemos, para realizar a remoo de um elemento da lista necessrio busc-lo. A funo buscaDup descrita anteriormente devolve um apontador para o elemento onde o valor procurado se encontra ou NULL caso o valor no ocorra na lista. Se a busca tem sucesso, ento o apontador devolvido aponta para a clula que se quer remover. Alm disso, essa clula tem acesso clula anterior e posterior da lista duplamente encadeada e isso tudo o que precisamos saber para realizar a remoo satisfatoriamente. A funo removeDup recebe como parmetros um apontador *dup para o incio da lista duplamente encadeada e um valor x , busca uma clula na lista que contm esse valor e, caso esse valor ocorra na lista, realiza sua remoo.
DCT
UFMS
61.4 R EMOO
427
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
void removeDup(celula **dup, int x) { celula *anterior, *posterior, *removido; removido = buscaDup(*dup, x); if (removido != NULL) { anterior = removido->ant; posterior = removido->prox; if (anterior != NULL) anterior->prox = posterior; else *dup = posterior; if (posterior != NULL) posterior->ant = anterior; free(removido); } else printf("Valor no se encontra na lista!\n"); }
Observe que os apontadores anterior , posterior e removido so variveis locais funo removeDup que auxiliam tanto na busca como na remoo propriamente. Alm disso, se a clula a ser removida a primeira da lista, h necessidade de alterar o contedo do apontador para o incio da lista dup para a clula seguinte desta lista. Observe ainda que o procedimento removeDup pode ser usado para remoo de uma clula em uma lista linear encadeada ordenada ou no ordenada.
Exerccios
61.1 Considere uma lista linear duplamente encadeada contendo os elementos l1 , l2 , . . . , ln , como na gura 61.3.
lista_d
l1
l2
ln
Figura 61.3: Uma lista linear duplamente encadeada. Escreva uma funo que altere os apontadores ant e prox da lista, sem mover suas informaes, tal que a lista que invertida como na gura 61.4.
DCT UFMS
61.4 R EMOO
428
lista_d
ln
ln1
l1
Figura 61.4: Inverso da lista linear duplamente encadeada da gura 61.3. 61.2 Escreva uma funo que receba um apontador para o incio de uma lista linear em alocao duplamente encadeada, um apontador para o m dessa lista e um valor, e realize a insero desse valor no nal da lista. 61.3 Suponha que voc queira manter uma lista linear duplamente encadeada com os contedos em ordem crescente. Escreva as funes de busca, insero e remoo para essa lista.
DCT
UFMS
R EFERNCIAS B IBLIOGRFICAS
[1] P. Breton. Histria da Informtica. Editora da UNESP, 1991. Traduo de Elcio Fernandes do original Histoire de Linformatique, 1987, Editions La Decouvert. 1 [2] Computer history from B.C. to today. http://www.computerhope.com/history/. ltimo acesso em 28 de janeiro de 2009. 1 [3] R. X. Cringely. A history of the computer. http://www.pbs.org/nerds/timeline/index.html. ltimo acesso em 28 de janeiro de 2009. 1 [4] P. Feoloff. Algoritmos em linguagem C. Editora Campus/Elsevier, 2009. 4.5, 4.5, 7.3, 17, 18, 20.2 [5] B. W. Kernighan and R. Pike. The Practice of Programming. Addison-Wesley Professional, 1999. [6] B. W. Kernighan and D. M. Ritchie. C Programming Language. Prentice Hall, 2nd edition, 1988. [7] K. N. King. C Programming A Modern Approach. W. W. Norton & Company, Inc., 2nd edition, 2008. 17, 18 [8] S. G. Kochan. Unix Shell Programming. Sams Publishing, 3rd edition, 2003. [9] J. Kopplin. An illustrated history of computers. http://www.computersciencelab.com/ComputerHistory/History.htm. ltimo acesso em 28 de janeiro de 2009. 1 [10] T. Kowaltowski. John von Neumann: suas contribuies computao. Revista de Estudos Avanados, 10(26), Jan/Abr 1996. Instituto de Estudos Avanados da Universidade de So Paulo. 2 [11] H. Lukoff. From Dits to Bits... a Personal History of the Electronic Computer. Hawley Books, 1979. 2.1 [12] Departamento de Cincia da Computao IME/USP, listas de exerccios Introduo Computao. http://www.ime.usp.br/~macmulti/. ltimo acesso em 28 de janeiro de 2009. 2 [13] A. Sapounov, E. Rosen, and J. Shaw. Computer history museum. http://www.computerhistory.org/. ltimo acesso em 28 de janeiro de 2009. 1 429
REFERNCIAS BIBLIOGRFICAS [14] R. L. Shackelford. Introduction to Computing and Algorithms. Addison Wesley, 1997. 1 [15] S. S. Skiena and M. Revilla. Programming Challenges. Springer, 2003.
430
[16] A. M. Turing. On computable numbers, with an application to the Entscheidungsproblem. Proceedings of the London Mathematical Society, 42(2):230265, 1936. 2.1, 2.4 [17] J. von Neumann. First draft of a report on the EDVAC. Technical report, Moore School of Electrical Engineering University of Pennsylvania, 1945. 2.1, 2.3 [18] Wikipedia the free encyclopedia. http://en.wikipedia.org/wiki/Main_Page. ltimo acesso em 28 de janeiro de 2009. 1 [19] K. Zuse. Verfahren zur selbstttigen durchfhrung von rechnungen mit hilfe von rechenmaschinen. Patentanmeldung Z 23 139 GMD Nr. 005/021, 1936. 2.1
DCT
UFMS