Esta é uma pré-visualização de arquivo. Entre para ver o arquivo original
UNIVERSIDADE DE CAXIAS DO SUL CENTRO DE CIÊNCIAS EXATAS E TECNOLÓGICAS DEPARTAMENTO DE INFORMÁTICA LINGUAGEM C Professor Maurício Galimberti Professor André Gustavo Adami LINGUAGEM C 2/103 Sumário 1. INTRODUÇÃO .............................................................................................................................. 8 1.1 VISÃO GERAL DE UM PROGRAMA C ........................................................................................... 8 2. ESTRUTURA BÁSICA DE UM PROGRAMA EM C ............................................................... 9 2.1 BIBLIOTECAS ............................................................................................................................. 9 3. TIPOS DE DADOS....................................................................................................................... 10 3.1 ENUMERAÇÃO ......................................................................................................................... 11 3.2 MODIFICADORES DE TIPO DE ACESSO ..................................................................................... 12 3.2.1 Const............................................................................................................................... 12 3.2.2 Volatile ........................................................................................................................... 12 3.3 CONSTANTES ........................................................................................................................... 12 3.4 CONSTANTES PRÉ-DEFINIDAS................................................................................................... 13 3.5 AS BIBLIOTECAS DISPONÍVEIS E ALGUMAS FUNÇÕES INTERESSANTES ...................................... 14 4. COMANDO DE ESCRITA ......................................................................................................... 16 4.1 MODIFICADORES DE SAÍDA...................................................................................................... 17 4.2 EXERCÍCIOS ............................................................................................................................. 18 5. VARIÁVEIS.................................................................................................................................. 19 5.1 NOME DE VARIÁVEIS............................................................................................................... 19 5.2 DECLARAÇÃO DE VARIÁVEIS................................................................................................... 19 5.2.1 Tipos de Declarações de Variáveis ................................................................................ 19 5.2.1.1 Variáveis locais ......................................................................................................................... 20 5.2.1.2 Parâmetros Formais................................................................................................................... 20 5.2.1.3 Variáveis Globais ...................................................................................................................... 20 5.3 PALAVRAS RESERVADAS ......................................................................................................... 21 6. OPERADORES ............................................................................................................................ 22 6.1 ATRIBUIÇÃO ............................................................................................................................ 22 6.1.1 Conversão de Tipos em Atribuições ............................................................................... 22 6.2 OPERADORES ARITMÉTICOS .................................................................................................... 23 6.2.1 Incremento e Decremento............................................................................................... 23 6.2.2 Operadores Aritméticos de Atribuição ........................................................................... 24 6.3 OPERADORES RELACIONAIS E LÓGICOS................................................................................... 24 6.4 OPERADORES BIT A BIT........................................................................................................... 24 6.5 OPERADOR ?............................................................................................................................ 25 6.6 OPERADORES DE PONTEIROS & E * ......................................................................................... 25 6.7 OPERADOR VÍRGULA............................................................................................................... 26 6.8 EXPRESSÕES............................................................................................................................ 26 6.8.1 Conversão de Tipos em Expressões................................................................................ 26 6.8.2 Casts ............................................................................................................................... 27 6.8.3 Espacejamento e Parênteses........................................................................................... 28 7. ALGUMAS FUNÇÕES DE E/S .................................................................................................. 29 7.1 FUNÇÃO SCANF() ..................................................................................................................... 29 7.2 FUNÇÃO GETCHE() E GETCH().................................................................................................. 30 7.3 LENDO E ESCREVENDO CARACTERES....................................................................................... 30 LINGUAGEM C 3/103 7.4 EXERCÍCIOS ............................................................................................................................. 31 8. COMANDOS CONDICIONAIS ................................................................................................. 32 8.1 COMANDO IF............................................................................................................................ 32 8.2 COMANDO SWITCH .................................................................................................................. 32 8.3 EXERCÍCIOS ............................................................................................................................. 33 9. COMANDOS REPITITIVOS OU LAÇOS................................................................................ 35 9.1 LAÇO FOR ................................................................................................................................ 35 9.2 LAÇO WHILE ............................................................................................................................ 35 9.3 LAÇO DO-WHILE ...................................................................................................................... 36 9.4 COMANDOS BREAK, CONTINUE E EXIT() .................................................................................. 36 9.5 EXERCÍCIOS ............................................................................................................................. 37 10. FUNÇÕES..................................................................................................................................... 39 10.1 LOCALIZAÇÃO DAS FUNÇÕES ................................................................................................... 40 10.1.1 Corpo da função antes do programa principal (no mesmo arquivo) ............................. 40 10.1.2 Corpo da função depois do programa principal (no mesmo arquivo) ........................... 40 10.1.3 Corpo da função escrito em arquivo separado .............................................................. 41 10.2 ARGUMENTOS PARA FUNÇÃO MAIN()....................................................................................... 42 10.3 PROTÓTIPO DE FUNÇÕES .......................................................................................................... 42 10.4 RETORNO DE PONTEIROS......................................................................................................... 42 10.5 NÚMEROS DE PARÂMETROS VARIÁVEL.................................................................................... 43 10.5.1 Funções para número variável de argumentos .............................................................. 43 10.6 CLASSES DE ARMAZENAMENTO .............................................................................................. 44 10.6.1 Classe de Armazenamento - auto ................................................................................... 44 10.6.2 Classe de Armazenamento - extern................................................................................. 44 10.6.3 Classe de Armazenamento - static .................................................................................. 45 10.6.3.1 Variáveis Locais static ............................................................................................................. 45 10.6.3.2 Variáveis Globais static............................................................................................................ 45 10.6.4 Classe de Armazenamento - register .............................................................................. 45 10.7 DIRETIVA #DEFINE................................................................................................................... 45 10.8 FUNÇÕES RECURSIVAS ............................................................................................................ 46 10.9 EXERCÍCIOS ............................................................................................................................. 46 11. VETORES E MATRIZES ........................................................................................................... 47 11.1 INICIALIZAÇÃO DE VETORES E MATRIZES................................................................................ 47 11.2 MATRIZES E VETORES COMO ARGUMENTO DE FUNÇÕES ......................................................... 48 11.3 LIMITES ................................................................................................................................... 48 11.4 STRINGS .................................................................................................................................. 49 11.4.1 Leitura de Strings ........................................................................................................... 49 11.4.1.1 Função scanf().......................................................................................................................... 49 11.4.1.2 Função gets()............................................................................................................................ 50 11.4.2 Escrita de Strings............................................................................................................ 50 11.4.2.1 Função printf() ......................................................................................................................... 50 11.4.2.2 Função puts() ........................................................................................................................... 50 11.4.3 Funções de Manipulação de Strings .............................................................................. 51 11.4.3.1 Função strlen() ......................................................................................................................... 51 11.4.3.2 Função strcat() ......................................................................................................................... 51 11.4.3.3 Função strcmp() ....................................................................................................................... 52 11.4.3.4 Função strcpy() ........................................................................................................................ 52 11.4.3.5 Função strchr() ......................................................................................................................... 52 11.4.3.6 Função strstr() .......................................................................................................................... 52 11.4.4 Exemplo Geral................................................................................................................ 52 LINGUAGEM C 4/103 11.5 EXERCÍCIOS ............................................................................................................................. 53 12. PONTEIROS................................................................................................................................. 55 12.1 EXPRESSÕES COM PONTEIROS ................................................................................................. 55 12.1.1 Atribuição de Ponteiros.................................................................................................. 55 12.1.2 Aritmética de Ponteiros .................................................................................................. 56 12.2 INICIALIZAÇÃO DE PONTEIROS................................................................................................. 56 12.2.1 Comparação de Ponteiros .............................................................................................. 57 12.3 PONTEIROS E MATRIZES .......................................................................................................... 57 12.3.1 Matrizes de Ponteiros ..................................................................................................... 58 12.3.2 Acessando partes de Matrizes como vetores .................................................................. 59 12.4 INDIREÇÃO MÚLTIPLA ............................................................................................................. 59 12.5 PONTEIROS PARA FUNÇÕES ..................................................................................................... 60 12.6 MAIS SOBRE DECLARAÇÕES DE PONTEIROS ............................................................................ 61 12.7 EXERCÍCIOS ............................................................................................................................. 63 13. ESTRUTURAS E UNIÕES.......................................................................................................... 64 13.1 ESTRUTURAS ........................................................................................................................... 64 13.1.1 Inicializando Estruturas ................................................................................................. 65 13.1.2 Estruturas Aninhadas ..................................................................................................... 65 13.1.3 Estruturas e funções ....................................................................................................... 66 13.1.4 Vetor de Estruturas......................................................................................................... 67 13.1.5 Ponteiros para Estruturas .............................................................................................. 67 13.2 CAMPOS DE BITS ..................................................................................................................... 68 13.3 UNIÕES.................................................................................................................................... 69 13.4 SIZEOF() .................................................................................................................................. 71 13.5 TYPEDEF ................................................................................................................................. 71 13.6 EXERCÍCIOS ............................................................................................................................. 72 14. ALOCAÇÃO DINÂMICA........................................................................................................... 73 14.1 FUNÇÕES DE ALOCAÇÃO DINÂMICA EM C ............................................................................... 73 14.1.1 Função malloc() ............................................................................................................. 73 14.1.2 Função calloc() .............................................................................................................. 74 14.1.3 Função free() .................................................................................................................. 75 14.1.4 Função realloc() ............................................................................................................. 76 14.2 MATRIZES DINAMICAMENTE ALOCADAS................................................................................. 76 14.3 LISTAS ENCADEADAS .............................................................................................................. 78 14.3.1 Listas Singularmente Encadeadas.................................................................................. 78 14.3.2 Listas Duplamente Encadeadas...................................................................................... 79 14.4 ÁRVORES BINÁRIAS ................................................................................................................ 81 14.5 EXERCÍCIOS ............................................................................................................................. 83 15. E/S COM ARQUIVO ................................................................................................................... 84 15.1 E/S ANSI X E/S UNIX ........................................................................................................... 84 15.2 STREAMS ................................................................................................................................. 84 15.3 ARQUIVOS ............................................................................................................................... 84 15.4 SISTEMA DE ARQUIVOS ........................................................................................................... 85 15.5 ESTRUTURA FILE.................................................................................................................... 85 15.6 ABERTURA DE ARQUIVOS........................................................................................................ 85 15.7 FECHAMENTO DE ARQUIVO ..................................................................................................... 86 15.8 LEITURA E GRAVAÇÃO DE CARACTERES.................................................................................. 86 LINGUAGEM C 5/103 15.8.1 Gravação ........................................................................................................................ 87 15.8.2 Leitura ............................................................................................................................ 87 15.9 VERIFICANDO FIM DE ARQUIVO ............................................................................................... 87 15.10 TRABALHANDO COM ARQUIVOS .............................................................................................. 88 15.11 TRABALHANDO COM STRINGS: FPUTS() E FGETS() ................................................................... 89 15.12 FUNÇÕES DE TRATAMENTO DE ARQUIVOS ............................................................................... 89 15.12.1 rewind() ...................................................................................................................... 89 15.12.2 ferror() ........................................................................................................................ 90 15.12.3 remove()...................................................................................................................... 90 15.12.4 fflush()......................................................................................................................... 91 15.12.5 Função ftell() .............................................................................................................. 91 15.13 LENDO E GRAVANDO REGISTROS ............................................................................................. 91 15.13.1 Escrita de um bloco de dados..................................................................................... 91 15.13.2 Leitura de um bloco de dados..................................................................................... 92 15.13.3 Utilizando os comandos de leitura e gravação de registros....................................... 92 15.14 ACESSO ALEATÓRIO................................................................................................................. 93 15.15 COMANDO DE GRAVAÇÃO EM MODO TEXTO FORMATADO ....................................................... 94 15.16 CONDIÇÕES DE ERRO ............................................................................................................... 95 15.17 STREAMS PADRÃO................................................................................................................... 95 15.18 EXERCÍCIO:.............................................................................................................................. 96 15.19 SISTEMA DE ARQUIVO TIPO UNIX........................................................................................... 96 15.19.1 open().......................................................................................................................... 97 15.19.2 creat() ......................................................................................................................... 98 15.19.3 close() ......................................................................................................................... 98 15.19.4 read() e write()............................................................................................................ 98 15.19.5 unlink() ..................................................................................................................... 100 15.19.6 lseek() e tell() ............................................................................................................ 100 15.19.7 Funções para manipulação de buffers ..................................................................... 100 16. BIBLIOGRAFIA ........................................................................................................................ 103 LINGUAGEM C 6/103 Lista de Figuras FIGURA 1.1 - CODIFICAÇÃO DE UM PROGRAMA C................................................................................... 8 FIGURA 12.1 INDIREÇÃO SIMPLES E MÚLTIPLA ..................................................................................... 60 FIGURA 14.1 EXEMPLO DE UMA ÁRVORE BINÁRIA................................................................................ 82 LINGUAGEM C 7/103 Lista de Tabelas TABELA 3.1 - TIPOS DE DADOS BÁSICOS.................................................................................................. 10 TABELA 3.2 - UTILIZAÇÃO DOS MODIFICADORES DE TIPOS DE DADOS ..................................................... 10 TABELA 3.3 - EXEMPLO DE CONSTANTES ................................................................................................ 13 TABELA 4.1 - CÓDIGO PARA OS CARACTERES ESPECIAIS ......................................................................... 16 TABELA 4.2 - CÓDIGOS PARA FORMATAÇÃO DE IMPRESSÃO.................................................................... 16 TABELA 6.1 - CONVERSÕES DE TIPOS E SEUS EFEITOS ............................................................................. 22 TABELA 6.2 - OPERADORES ARITMÉTICOS .............................................................................................. 23 TABELA 6.3 - OPERADORES ARITMÉTICOS DE ATRIBUIÇÃO ..................................................................... 24 TABELA 6.4 - OPERADORES RELACIONAIS E LÓGICOS.............................................................................. 24 TABELA 6.5 - OPERADORES BIT-A-BIT..................................................................................................... 24 TABELA 6.6 - EXEMPLO DE OPERAÇÕES DE DESLOCAMENTO BIT-A-BIT................................................... 25 TABELA 7.1 - CÓDIGOS DE FORMATAÇÃO DO COMANDO SCANF() ........................................................... 29 TABELA 15.1 - FUNÇÕES MAIS COMUNS DO SISTEMA DE ARQUIVO COM BUFFER ..................................... 85 TABELA 15.2 - OS MODOS DE ABERTURA VÁLIDOS ................................................................................. 86 TABELA 15.3 – MACROS DEFINIDAS EM STDIO.H PARA AS POSIÇÕES PERMITIDAS NA FUNÇÃO FSEEK().93 TABELA 15.3 - FUNÇÕES DE E/S TIPO UNIX SEM BUFFER ...................................................................... 96 TABELA 15.4 MODOS DE ABERTURA DE ARQUIVOS EM BAIXO NÍVEL ...................................................... 97 LINGUAGEM C Introdução 8/103 1. Introdução A linguagem C foi criada por Dennis M. Ritchie e Ken Thompson no Laboratório Bell em 1972, baseada na linguagem B de Thompson que era uma evolução da antiga BCPL. A linguagem C tornou-se muito popular devido a características como: • O C é uma linguagem de alto nível com uma sintaxe bastante estruturada e flexível tornando sua programação bastante simplificada. • Programas em C são compilados, gerando programas executáveis. • O C compartilha recursos tanto de alto quanto de baixo nível, pois permite acesso e programação direta do microprocessador. Com isto, rotinas cuja dependência do tempo é crítica, podem ser facilmente implementadas usando instruções em Assembly. Por esta razão o C é a linguagem preferida dos programadores de aplicativos. • O C é uma linguagem estruturalmente simples e de grande portabilidade. O compilador C gera códigos mais enxutos e velozes do que muitas outras linguagens. • Embora estruturalmente simples (poucas funções intrínsecas) o C não perde funcionalidade pois permite a inclusão de uma farta quantidade de rotinas do usuário. Os fabricantes de compiladores fornecem uma ampla variedade de rotinas pré-compiladas em biblioteca Como uma ferramenta poderosa a linguagem C é usada na construção de vários aplicativos como sistemas operacionais, planilhas eletrônicas, processadores de texto, sistemas de transmissão de dados, entre outros. Um exemplo clássico é o sistema operacional UNIX, o qual foi implementado em C. No início da década de 80 a linguagem C é padronizada pelo American National Standard Institute: surge o ANSI C. Atualmente, a linguagem C vem sendo usada para desenvolver novas linguagens, entre elas a linguagem C++ e Java. 1.1 Visão geral de um programa C A geração do programa executável a partir do programa fonte obedece a uma seqüência de operações ante de tornar-se um executável. Depois de escrever o módulo fonte em um editor de textos, o programador aciona o compilador. Essa ação desencadeia uma seqüência de etapas, cada qual traduzindo a codificação do usuário para uma forma de linguagem de nível inferior, que termina com o executável criado pelo linkador. FIGURA 1.1 - Codificação de um programa C Editor (módulo fonte em C) Pré-processador (novo fonte expandido) Compilador (arquivo objeto) Linkador (executável) LINGUAGEM C Estrutura Básica de um Programa em C 9/103 2. Estrutura Básica de um Programa em C Um programa C consiste em uma ou várias “funções”. Baseado nesta afirmação, deve existir um mecanismo que garanta que todos os programas inicializem da mesma forma. Este mecanismo pode ser entendido como a padronização da função que será chamada primeiramente. Tal função chama-se main(). Sintaxe: main() primeira função a ser executada { inicia o corpo da função } termina a função Abaixo relacionam-se algumas regras para a programação em C: • Toda função C deve ser iniciada e encerrada por um abre chaves e um fecha chaves respectivamente; • O nome da função, os parênteses e as chaves são os únicos elementos obrigatórios de uma função; • Todas as instruções devem estar dentro das chaves; • As instruções C são sempre encerradas por um ponto-e-vírgula; 2.1 Bibliotecas Para não ter que criar programas grandes devido a linkedição de um arquivo objeto a linguagem inclui as bibliotecas para que no momento da compilação seja gerado um programa com as funções utilizadas. Para fazer isto, usa-se uma diretiva de compilação a qual é colocada fora de qualquer função do programa. A diretiva é #include instrui o compilador a ler outro arquivo-fonte. Sintaxe: #include nome_arq onde: nome_arq tem que estar entre aspas ou símbolos de maior ou menor Em algumas plataformas existe somente uma das duas formas. No caso da plataforma DOS/Windows 95, aspas significa para o compilador que a biblioteca em questão deve ser buscada primeiramente no diretório atual e depois no diretório padrão do turbo ou pelas variáveis de ambiente. Também, os caracteres “<>“ significam ao compilador que deve procurar somente no diretório padrão ou nos diretórios definidos pelas variáveis de ambiente. Exemplo 2.1 #include <stdio.h> Não se usa o ponto-e-vírgula após diretivas de compilação. Este tipo de declaração é muito usado nos programas profissionais. Cada comando pré-definido em C está prototipado em alguma biblioteca (isto é informado conjuntamente ao comando). LINGUAGEM C Tipos de Dados 10/103 3. Tipos de Dados Em C existem 5 tipos de variáveis básicas. Nos computadores da linha IBM-PC (sistema operacional 16 bits) a tabela 3.1 é válida. Tabela 3.1 - Tipos de dados básicos TIPO BIT FAIXA MÍNIMA char 8 -128 a 127 int 16 - 32768 a 32767 float 32 3.4E-38 a 3.4E+38 double 64 1.7E-308 a 1.7E+308 void 0 sem valor Com exceção de void, os tipos de dados básicos podem estar acompanhados por modificadores na declaração de variáveis. Os modificadores de tipos da linguagem C são: • signed; • unsigned; • long; • short. Os modificadores signed, short, long e unsigned podem ser aplicados aos tipos básicos caractere e inteiro. Contudo, long também pode ser aplicado a double. A tabela 3.2 mostra todas a combinações de tipos de dados Tabela 3.2 - Utilização dos modificadores de tipos de dados TIPO BIT FAIXA MÍNIMA unsigned char 8 0 a 255 signed char 8 -128 a 127 unsigned int 16 0 a 65535 signed int 16 O mesmo que int short int 16 O mesmo que int unsigned short int 16 0 a 65535 signed short int 16 O mesmo que short int long int 32 -2.147.483.647 a 2.147.483.647 signed long int 32 -2.147.483.647 a 2.147.483.647 unsigned long int 32 0 a 4.294.967.925 long double 128 Dez dígitos de precisão O uso de signed com inteiros é redundante. No entanto, ele é permitido porque a declaração default de inteiros assume um número com sinal. O uso mais importante de signed é modificar char em implementações em que esse tipo, por padrão, não tem sinal. Algumas implementações podem permitir que unsigned seja aplicado aos tipos de ponto flutuante (como em unsigned double). Porém, isso reduz a portabilidade de seu código e geralmente não é recomendado. O modificador unsigned altera o valor da faixa mínima do tipo através do uso do bit mais significativo (indicador de sinal). No caso do sistema operacional ser 32 bits o tipo int será o único a ser alterado, pois ele passa de 2 bytes para 4 bytes de armazenamento. Isto também faz com que o intervalo de valores seja aumentado. Exemplo 3.1 main() { int qtde; char tam; float total; qtde = 2; tam = ‘G’; total = 20.70; printf(“Comprei %d camisas de tamanho %c.”, qtde, tam); LINGUAGEM C Tipos de Dados 11/103 printf(“\nNo total, paguei R$ %f.”, custo); } Execução: Comprei 2 camisas de tamanho G. No total, paguei R$ 20.70. As variáveis podem ser inicializadas no momento em que se faz a declaração das mesmas. Pode- se ver isto usando o programa anterior, que a execução será a mesma da versão anterior. main() { int qtde=2; char tam=‘G’; float total=20.70; printf(“Comprei %d camisas de tamanho %c.”, qtde, tam); printf(“\nNo total, paguei R$ %f.”, custo); } 3.1 Enumeração Enumeração é um conjunto de constantes inteiras que especifica todos os valores legais de uma variável desse tipo pode ser. A forma geral para enumeração é: Sintaxe: enum nome { lista_de_enumeração } lista_de_variáveis; Aqui, tanto o nome da enumeração quanto a lista de variáveis são opcionais. O nome da enumeração é usado para declarar variáveis daquele tipo. Com isso pode-se declarar as cores Exemplo 3.2 enum cores {amarelo, verde, vermelho}; enum cores semaforo; Dada essa definição e declaração, os tipos de comandos seguintes são perfeitamente válidos: semaforo = verde; if (semaforo==verde) printf(“Passagem permitida \n”); Para melhor compreensão da enumeração entende-se que cada símbolo representa um valor inteiro. O valor do primeiro símbolo da enumeração é 0. Assim, printf (“%d %d”, verde, vermelho); mostra 1 2 na tela. Como extensão, pode-se inicializar os símbolos de forma alternada para algum problema específico. Exemplo 3.3 enum cores { amarelo, verde=10, vermelho }; Agora os valores destes símbolos são amarelo 0 verde 10 vermelho 11 LINGUAGEM C Tipos de Dados 12/103 3.2 Modificadores de Tipo de Acesso O padrão ANSI introduziu dois novos modificadores de tipo que controlam a maneira como a variáveis podem ser acessadas ou modificadas. Esses modificadores são const e volatile. Devem preceder os modificadores de tipo e os nomes que eles modificam. 3.2.1 Const Variáveis do tipo const não podem ser modificadas por seu programa (por isso ela recebe um valor inicial). Exemplo 3.4 const int a=10; O exemplo 5.4 cria uma variável inteira chamada a, com um valor inicial 10, que seu programa não pode modificar. Um exemplo do uso do const é para verificar se uma variável em particular é modificada pelo seu programa. 3.2.2 Volatile O modificador volatile é usado para informar ao compilador que o valor de uma variável pode ser alterado de maneira não explicitamente especificada pelo programa. Por exemplo, um endereço de uma variável global pode ser passado para a rotina de relógio do sistema operacional e usado para guardar o tempo real do sistema. Nessa situação, o conteúdo de uma variável é alterado sem nenhum comando de atribuição explicito no programa. Isso ajuda o programa no sentido de avisar ao compilador que o conteúdo de uma variável é mutável, mesmo que sua referência não aparecer no lado esquerdo da expressão. É possível usar const e volatile juntos. Por exemplo, se 0x30 é assumido como sendo o valor de uma porta que é mudado por condições externas. Para evitar efeitos colaterais deve-se declarar da seguinte forma: const volatile unsigned char *port = 0x30; 3.3 Constantes Uma constante tem valor fixo e inalterável durante a execução do programa. Isto pode ser exemplificado pelos exemplos 3.1 e 3.2 da função printf(). Em uma constante caractere é escrita entre aspas simples (‘’), uma constante cadeia de caracteres entre aspas duplas (“”) e constantes numéricas como o número propriamente dito. Exemplo 3.5 ‘C’ “programa” 8 465.67 LINGUAGEM C Tipos de Dados 13/103 Constantes em C podem ser de qualquer um dos cinco tipos de dados básicos. A maneira como cada constante é representada depende do seu tipo. Pode-se especificar precisamente o tipo da constante numérica através da utilização de um sufixo. Para tipos em ponto flutuante coloca-se um F após o número, ele será tratado como float. Se for colocado um L, ele tornar-se-á um long double. Para tipos inteiros, o sufixo U representa unsigned e o L representa long. A tabela 5.3 mostra alguns exemplos de constantes. Tabela 3.3 - Exemplo de constantes Tipo de Dado Exemplo de Constantes int 1 123 21000 -234 long int 35000L -34L short int 10 -12 90 unsigned int 10000U 987U 40000“ float 123.23F 2.34e-3F Double 123.23 12312333 -0.9876324 long double 1001.2L Além deste tem-se as constantes Hexadecimais e Octais. Usam-se tais sistemas numéricos para facilitar a programação. Uma constante hexadecimal deve consistir em um 0x seguido por uma constante na forma hexadecimal. Uma constante octal começa com 0. Exemplo 3.6 int hex = 0x80; /* 128 em decimal */ int oct = 012; /* 10 em decimal */ 3.4 Constantes pré-definidas Em alguns compiladores C, algumas constantes simbólicas já estão pré-definidas. Estas constantes em geral definam alguns valores matemáticos (pi, pi/2, e, etc.), limites de tipos etc. A seguir segue uma tabela contendo algumas (existem muitas outras) constantes simbólicas pré-definidas no compilador Turbo C++ da Borland. Biblioteca Constante Valor Significado math.h M_PI 3.14159... pi math.h M_PI_2 1.57079... pi/2 math.h M_PI_4 0,78539... pi/4 math.h M_1_PI 0,31830... 1/pi math.h M_SQRT2 1,41421... √2 conio.h BLACK 0 valor da cor (preto) conio.h BLUE 1 valor da cor (azul) conio.h GREEN 2 valor da cor (verde) conio.h CYAN 3 valor da cor (cyan) conio.h RED 4 valor da cor (vermelho) conio.h MAGENTA 5 valor da cor (magenta) limits.h INT_MAX 32767 limite superior do tipo int limits.h INT_MIN -32768 limite inferior do tipo int LINGUAGEM C Tipos de Dados 14/103 3.5 As bibliotecas disponíveis e algumas funções interessantes A seguir segue uma lista de algumas as bibliotecas disponíveis no compilador Turbo C++ Borland: Ao longo do texto veremos o uso de muitas funções cobrindo uma boa parte destas bibliotecas, porém o leitor que desejar tornar-se "fluente" na linguagem C pode (e deve) estudá-las com profundidade. alloc.h assert.h bcd.h bios.h complex.h conio.h ctype.h dir.h dirent.h dos.h errno.h fcntl.h float.h fstream.h generic.h graphics.h io.h iomanip.h iostream.h limits.h locale.h malloc.h math.h mem.h process.h setjmp.h share.h signal.h stdarg.h stddef.h stdio.h stdiostr.h stdlib.h stream.h string.h strstrea.h sys\stat.h sys\timeb.h sys\types.h time.h values.h Vejamos algumas funções disponíveis nas bibliotecas C. Biblioteca math.h int abs(int i); double fabs(double d); Calcula o valor absoluto do inteiro i e do real d, respectivamente. double sin(double arco); double cos(double arco); double tan(double arco); double asin(double arco); double acos(double arco); double atan(double arco); Funções trigonométricas do ângulo arco, em radianos. double ceil(double num); double floor(double num); Funções de arredondamento para inteiro. ceil() arredonda para cima. Ex. ceil(3.2) == 4.0; floor() arredonda para baixo. Ex. floor(3.2) == 3.0; LINGUAGEM C Tipos de Dados 15/103 double log(double num); double log10(double num); Funções logarítmicas: log() é logaritmo natural (base e), log10() é logaritmo decimal (base 10). double pow(double base, double exp); Potenciação: pow(3.2,5.6) = 3.25.6. double sqrt(double num); Raiz quadrada: sqrt(9.0) = 3.0. Biblioteca stdlib.h int random(int num); Gera um número inteiro aleatório entre 0 e num - 1. LINGUAGEM C Comando de Escrita 16/103 4. Comando de Escrita A função printf() é uma das funções de E/S (entrada e saída) que mostra na tela os parâmetros que são passados como argumento. Seu protótipo está na biblioteca stdio.h. Sintaxe: printf(“<expressao_de_controle>”, lista_de_argumentos) onde: expressão_de_controle = contém caracteres que serão exibidos na tela e códigos de formatação que indicam o formato em que os argumentos (da lista_de_argumentos) devem ser impressos. lista_de_argumentos = lista de argumentos que serão apresentadas na expressão_de_controle. Tal lista não tem tamanho máximo e cada argumento deve ser separado por uma vírgula. A tabela 4.1 mostra os códigos na linguagem para caracteres que não podem ser inseridos diretamente do teclado. Tabela 4.1 - Código para os caracteres especiais CÓDIGOS ESPECIAIS SIGNIFICADO \n Nova Linha (LF) \t Tab (HT) \b Retrocesso (BS) \” ” \\ Barra invertida \f Salta Página de Formulário (FF) \r Retorno de Carro (CR) \’ ’ \v Tabulação Vertical \a Alerta (beep) \N Constante octal (onde N é uma constante octal) \xN Constante Hexadecimal (onde N é uma constante hexadecimal) \0 Nulo Além destes, existem os códigos para formatação de impressão dos argumentos passados para a função, os quais estã relacionados na tabela 4.2. Tabela 4.2 - Códigos para formatação de impressão CÓDIGO printf() FORMATO %c Caractere Simples %d ou %i Decimal %e ou %E (minúsculo ou maiúsculo) Notação Científica %f Ponto Flutuante %g ou %G (minúsculo ou maiúsculo) %e ou %f (o mais curto) %o Octal %s Cadeia de Caracteres %u Decimal sem Sinal %x ou %X (minúsculo ou maiúsculo) Hexadecimal %% Escreve o símbolo % LINGUAGEM C Comando de Escrita 17/103 Exemplo 4.1 main() { printf(“A %s C foi criada em %d \nOk?”, “Linguagem”, 1972); } Execução:: A Linguagem C foi criada em 1972 Ok? No exemplo 3.1 percebe-se o uso de dois tipos de códigos: primeiro de formatação com %s (para strings) representando a constante string “Linguagem” e %d (decimais) representando a constante ano 1972; segundo um código especial para representar caracter de nova linha \n. Exemplo 4.2 main() { printf(“A %s %c foi ”, “Linguagem”, ‘C’); printf(“ criada em %d ”, 1972); } Execução: A Linguagem C foi criada em 1972 No exemplo 3.2, nota-se que ‘C’ é delimitado por aspas simples enquanto que “Linguagem” é delimitado por aspas duplas. Isto indica ao compilador como diferenciar um caractere de uma cadeia de caracteres. 4.1 Modificadores de Saída Para complementar os códigos de formatação a linguagem C oferece alguns códigos especiais para melhorar a apresentação dos dados. Para isto relacionam-se: • ‘-’ : alinhamento pela esquerda. • ‘+’: coloca o sinal de menos ou mais se o valor for numérico. • n (número) : indica o tamanho mínimo do campo. O preenchimento é feito com espaços em branco. Se o tamanho iniciar com 0 (ex. 05), o preenchimento passa a ser feito com zeros. • .m : tamanho máximo do campo, menos para os reais onde é usado para indicar o número de casas decimais. • * : substitui um dos n, mas o seu valor é o próximo parâmetro. • ‘l’ : indica que segue um dado longo. Exemplo 4.3 COMANDO TELA printf(“%s %d”, “Numero”,10); Numero 10 printf(“%8s %4d”, “Numero”,10); ##Numero ##10 printf(“%-8s %4d”, “Numero”,10); Numero## ##10 printf(“%-3.5s %2.2f”,“Numero”,10.99); Numer 10.99 printf(“Long %ld”, 57485784); Long 57485784 onde: # - representa 1 espaço em branco. LINGUAGEM C Comando de Escrita 18/103 4.2 Exercícios 1. Escreva os comandos de saída formatada que gerem as seguintes saídas: 01234567890123456789012 a) Camisa estampada 40,00 Camisa lisa 35,00 b) Camisa estampada 40,00 Camisa lisa 35,00 c) Camisa estampada 00040,00 Camisa lisa 00035,00 d) Camisa e 40 Camisa l 35 LINGUAGEM C Variáveis 19/103 5. Variáveis Variável em C é um espaço de memória reservado para armazenar um certo tipo de dado e tendo um nome para referenciar o seu conteúdo. O conteúdo da mesma pode variar segundo os comandos de alteração do programa. Exemplo 5.1 main() { int ano; ano = 1972; printf(“A Linguagem C foi criada em %d ”, ano); } A primeira instrução, int ano; é um exemplo de declaração de variável, isto é, apresenta um tipo, int, e um nome, ano. A segunda instrução, ano = 1972; atribui um valor à variável e este valor será acessado através de seu nome. Para realizar isto foi usado o operador de atribuição (=). 5.1 Nome de Variáveis Regras: • Uma variável não pode ter o mesmo nome de uma palavra-chave de C. • Em C, letras maiúsculas diferem das letras minúsculas. • O número de caracteres válidos é definido de ambiente para ambiente. 5.2 Declaração de Variáveis A declaração de variáveis serve para reservar uma quantidade de memória apropriada para armazenar o tipo especificado. Tal declaração consiste no nome de um tipo, seguido do nome da variável. Em C todas as variáveis devem ser declaradas. Se existir mais de uma variável do mesmo tipo, pode-se declará-la de uma vez separando seus nomes por vírgulas. int ano, mes, dia; 5.2.1 Tipos de Declarações de Variáveis O tipo de declaração de variável é definido pela localização onde a mesma é declarada. Existem três lugares básicos: dentro de funções, na definição dos parâmetros das funções e fora de todas as funções. Estas são variáveis locais, parâmetros formais e variáveis globais, respectivamente. LINGUAGEM C Variáveis 20/103 5.2.1.1 Variáveis locais Variáveis que são declaradas dentro de uma função. Tais variáveis só podem ser referenciadas por comandos que estão dentro do bloco no qual as variáveis foram declaradas, isto é, não são reconhecidas fora de seu próprio bloco de código. Exemplo 5.2 void main() { int num; num = 10; } 5.2.1.2 Parâmetros Formais Variáveis que são declaradas para passagem de parâmetros em funções. Exemplo 5.3 int soma(int x, int y) { return (x+y); } 5.2.1.3 Variáveis Globais Variáveis que são declaradas fora de qualquer função. Tais variáveis são reconhecidas pelo programa inteiro e podem ser usadas por qualquer pedaço de código. Exemplo 5.4 int num=10; void main() { printf(“%d”, num * num); } Exemplo 5.5 int num=2; int media(int y, int x) { return ((x+z)/num); } void main() { int x1 = 15; int x2 = 4; printf(“%d %d ”, num * num, media(x1,x2)); } LINGUAGEM C Variáveis 21/103 5.3 Palavras reservadas Existem certos nomes que não podem ser usados como identificadores. São chamadas as palavras reservadas e são de uso restrito da linguagem C (comandos, estruturas, declarações, etc.). O conjunto de palavras reservadas usadas em C é o seguinte: asm auto break case cdecl char class const continue _cs default delete do double _ds else enum _es extern _export far _fastcall float for friend goto huge if inline int interrupt _loadds long near new operator pascal private protected public register return _saveregs _seg short signed sizeof _ss static struct switch template this typedef union unsigned virtual void volatile while LINGUAGEM C Operadores 22/103 6. Operadores A linguagem C é muito rica em operadores internos. C define quatro classes de operadores: aritméticos, relacionais, lógicos e bit a bit. Além disso, C tem alguns operadores especiais para tarefas particulares. 6.1 Atribuição Para o operador de atribuição é utilizado o símbolo =. Além disto, a linguagem oferece um tipo de atribuição múltipla, isto é, em um comando só pode-se atribuir o mesmo valor a muitas variáveis. Exemplo 6.1 x = 10; a = b = c = 10; 6.1.1 Conversão de Tipos em Atribuições A linguagem C permite a conversão automática de tipos, o que não acontece em Pascal, por exemplo. Conversão de tipos refere-se à situação em que variáveis de um tipo são misturadas com varáveis de outro tipo. Em um comando de atribuição, a regra de conversão de tipos é muito simples: o valor do lado direito (lado da expressão) de uma atribuição é convertido no tipo do lado esquerdo (variável destino), como no exemplo 6.2: Exemplo 6.2 void main() { int x; char ch; float f; ch = x; // Linha 1 x = f; // Linha 2 f = ch; // Linha 3 f = x; // Linha 4 } Na linha 1, os bits mais significativos da variável inteira x são ignorados, deixando ch com os 8 bits menos significativos. Se x está entre 255 e 0, então ch e x têm valores idênticos. De outra forma, o valor de ch reflete apenas os bits menos significativos de x. Na linha 2, x recebe a parte inteira de f. Na linha 3, f converte o valor inteiro de 8 bits armazenado em ch no mesmo valor em formato de ponto flutuante. Isso também acontece na linha 4, exceto por f converter um valor inteiro de 16 bits no formato de ponto flutuante. Na tabela 6.1 mostra-se algumas conversões de tipos e seus efeitos. Para essas conversões foi assumido uma palavra de 16 bits. Tabela 6.1 - Conversões de tipos e seus efeitos Tipo Destino Tipo da Expressão Possível Informação Perdida signed char char Se valor > 127, o destino é negativo char short int Os 8 bits mais significativos char int Os 8 bits mais significativos char long int Os 24 bits mais significativos int long int Os 16 bits mais significativos int float A parte fracionária e possivelmente mais float double Precisão, o resultado é arredondado double long double Precisão, o resultado é arredondado LINGUAGEM C Operadores 23/103 Para utilizar a tabela acima a fim de fazer uma conversão não mostrada, simplesmente converta um tipo por vez até acabar. por exemplo, para converter double em int, primeiro converta double em float e, então, float em int. 6.2 Operadores Aritméticos A tabela 6.2 mostra os operadores aritméticos suportados pela linguagem. Tabela 6.2 - Operadores aritméticos Operador Ação - Subtração, também menos unário + Adição * Multiplicação / Divisão % Módulo da Divisão (resto) -- Decremento ++ Incremento O menos unário multiplica seu único operando por -1. Isso é, qualquer número precedido por um sinal Quando / é aplicado a um inteiro ou caractere, qualquer resto é truncado. O operador módulo % também, trabalha em C da mesma forma que em outras linguagens, devolvendo o resto de uma divisão inteira. Contudo, % não pode ser usado nos tipos em ponto flutuante. 6.2.1 Incremento e Decremento A linguagem C inclui dois operadores que geralmente não encontramos em outras linguagens. São os operadores de incremento (++) e decremento (--), os quais somam 1 ao seu operando, e subtraem 1 de seu operando, respectivamente. x++; ou ++x; ou x = x + 1; x--; ou --x; ou x = x - 1; Quando usados em uma expressão tem seus efeitos alterados pela posição do sinal de decremento e incremento. Se o operador de incremento ou decremento preceder seu operando, C executa a operação de incremento ou decremento antes de usar o valor do operando. Se o operador estiver após seu operando, C usa o valor do operando antes de incrementá-lo ou decrementá-lo. Exemplo 6.3 x = 10; y = ++x; /*y recebe 11*/ x = 10; y = x++; /* y recebe 10 */ A precedência dos operadores aritméticos é a seguinte: ++ -- Mais alta - * / % + - Mais baixa Os operadores do mesmo nível de precedência são avaliados pelo compilador da esquerda para a direita. LINGUAGEM C Operadores 24/103 6.2.2 Operadores Aritméticos de Atribuição A tabela 6.3 mostra os operadores aritméticos de atribuição suportados pela linguagem. Tabela 6.3 - Operadores aritméticos de atribuição Operador Ação x -= y x = x - y x += y x = x + y x *= y x = x * y x /= y x = x / y x %= y x = x % y As expressões com este operadores são mais compactas e normalmente produzem um código de máquina mais eficiente. 6.3 Operadores Relacionais e Lógicos A tabela 6.4 mostra os operadores relacionais e lógicos suportados pela linguagem C. Tabela 6.4 - Operadores relacionais e lógicos Operador Ação Operador Ação > Maior que && AND >= Maior ou igual que || OR < Menor que ! NOT <= Menor ou igual que == Igual != Diferente Em C, verdadeiro é qualquer valor diferente de zero, e falso é zero. As expressões que usam operadores relacionais ou lógicos devolvem zero para falso e 1 para verdadeiro. Lembre-se de que toda expressão relacional e lógica produz como resiltado 0 ou 1. A precedência dos operadores relacionais e lógicos é a seguinte: ! Mais alta > >= < <= == != && || Mais baixa 6.4 Operadores Bit a Bit A tabela 6.5 mostra os operadores bit a bit suportados pela linguagem. Tabela 6.5 - Operadores bit-a-bit Operador Ação & AND | OR ^ OR exclusivo (XOR) ~ Complemento de um >> Deslocamento à direita << Deslocamento à esquerda LINGUAGEM C Operadores 25/103 A tabela 6.6 mostra o resultado de operações com deslocamento. Tabela 6.6 - Exemplo de operações de deslocamento bit-a-bit char x; x a cada execução da sentença Valor de x x =7 00000111 7 x << 1 00001110 14 x << 3 01110000 112 x << 2 11000000 192 x >> 1 01100000 96 x >> 2 00011000 24 Os operadores bit a bit só podem ser utilizados sobre um byte ou uma palavra, isto é, aos tipos de dados char e int e variantes do padrão C. Operações bit não podem ser usadas em float, double, long double, void ou outros tipos mais complexos. 6.5 Operador ? O operador ? substitui sentenças da forma Se-então-senão. Sintaxe: Exp1 ? Exp2 : Exp3; Onde Exp1, Exp2 e Exp3 são expressões. Onde Exp1 é avaliada e se a mesma for verdadeira, então Exp2 é avaliada e se torna o valor da expressão. Se Exp1 é falso, então Exp3 é avaliada e se torna o valor da expressão. Exemplo 6.4 x = 10; y = x > 9 ? 100 : 200; No exemplo 6.4, y recebe o valor 100, porque x (valor é 10) é maior que 9. Uma expressão equivalente seria x = 10; if (x > 9) y = 100; else y = 200; 6.6 Operadores de Ponteiros & e * Um ponteiro é um endereço na memória de uma variável. Uma variável de ponteiro é uma variável especialmente declarada para guardar um ponteiro para seu tipo especificado. O primeiro operador de ponteiro é &. Ele é um operador unário que devolve o endereço na memória de seu operando. Por exemplo, m = &cont; atribui o endereço de memória da variável cont em m. Este tipo de operando não pode ser utilizado em três casos: 1. &(cont + 4) - sempre associa-se a uma variável e não expressão; 2. &3 - constantes não são válidas; LINGUAGEM C Operadores 26/103 3. variáveis declaradas com classe de armazenamento register (não existe endereço para registrador). O segundo operador é *. Ele é um operador unário que devolve o valor da variável localizada no endereço que o segue. Por exemplo, se m contém o endereço da variável cont, q = *m; coloca o valor de cont em q. Os seguintes operadores * e & colocam o valor 10 na variável chamada target. O resultado (o valor 10) deste programa é mostrado na tela. Exemplo 6.5 #include “stdio.h” void main() { int target, source; int *m; source = 10; m = &source; target = *m; printf(“%d”,target); } 6.7 Operador Vírgula O operador vírgula é usado para encadear diversas expressões. O lado esquerdo de um operador vírgula é sempre avaliado como void. Isso significa que a expressão do lado direito se torna o valor de toda a expressão separada por vírgulas. Exemplo 6.6 x = (y = 3, y + 1); No exemplo 6.5, primeiro y recebe 3 e, em seguida, atribui o valor 4 a x. 6.8 Expressões Operadores, constantes e variáveis são os elementos que constituem as expressões. Uma expressão é qualquer combinação válida desses elementos. 6.8.1 Conversão de Tipos em Expressões Quando constantes e variáveis de tipos diferentes são misturadas em uma expressão, elas são convertidas a um mesmo tipo. O compilador C converte todos os operandos no tipo do maior operando, o que é denominado promoção de tipo. Isso é feito operação por operação, como descrito nas regras de conversão de tipos abaixo. SE um operando é long double ENTÃO o segundo é convertido para long double. SENÃO, SE um operando é double ENTÃO o segundo é convertido para double. SENÃO, SE um operando é float LINGUAGEM C Operadores 27/103 ENTÃO o segundo é convertido para float. SENÃO, SE um operando é unsigned long ENTÃO o segundo é convertido para unsigned long. SENÃO, SE um operando é long ENTÃO o segundo é convertido para long. SENÃO, SE um operando é unsigned ENTÃO o segundo é convertido para unsigned. Há ainda um caso adicional especial: se um operando é long e o outro é unsigned, e se o valor do unsigned não pode ser representado por um long, os dois operandos são convertidos para unsigned long. Considere as conversões de tipo que ocorrem no exemplo 6.6. Primeiro, o caractere ch é convertido para um inteiro e float f é convertido para double. Em seguida, o resultado de ch/i é convertido para double porque f*d é double. O resultado final é double porque, nesse momento, os dois operandos são double. Exemplo 6.7 char ch; int i; float f; double d; result= ( ch / i ) + ( f * d ) - ( f + i ); int double float double double 6.8.2 Casts A linguagem permite que uma expressão pode ser forçada a ser de um tipo especificado usando uma construção chamada cast. A forma geral de um cast é (tipo) expressão onde um tipo é um tipo de dado padrão de C. Por exemplo, para ter certeza de que a expressão x /2 será do tipo float, escreve-se (float) x/2; Neste caso se a variável x fosse um inteiro ímpar sem o cast seu valor reria um inteiro (o que não seria verdadeiro). Entretanto, com o uso do Cast oa variável x é definida como um float o que tornará o seu resultado um float. Como um operador, um cast é unário e tem a mesma precedência de qualquer outro operador unário. LINGUAGEM C Operadores 28/103 6.8.3 Espacejamento e Parênteses A linguagem C não limita o espaçamento ou tabulações em uma expressão. Ajudam a aumentar a legibilidade do programa. O excesso de parênteses não causa erros, isto é, colocar parênteses onde não necessita, não provoca erros, mas dificulta a leitura do programa. LINGUAGEM C Algumas funções de E/S 29/103 7. Algumas funções de E/S Neste capítulo será visto algumas funções como scanf(), getche(), getch(), getchar() e putchar(). Tais funções encontram-se no arquivo stdio.h, exceto getch() e getche() que encontram-se seus protótipos em conio.h. 7.1 Função scanf() Esta função serve para ler dados formatados da entrada padrão (teclado). Sua sintaxe é similar à de printf(), isto é, uma expressão de controles seguida por uma lista de argumentos separados por vírgulas. A principal diferença é que os argumentos de scanf() devem ser endereços de variáveis. Para enviar o endereço de cada argumento utiliza-se o operador &. Sintaxe: scanf(“expressão_de_controle”,lista_de_argumentos) A expressão de controle pode conter códigos de formatação, precedidos por um sinal %. Além disso, o caractere * colocado após o % que avisa à função que deve ser lido um valor do tipo indicado pela especificação, mas não deve ser atribuído a nenhuma variável (não deve ter parâmetros na lista de argumentos para estas especificações). Tabela 7.1 - Códigos de formatação do comando scanf() CÓDIGO printf() FORMATO %c Lê um único caractere simples %d ou %i Lê um inteiro decimal %e Lê um número em notação científica %f Lê um número em ponto flutuante %g Lê um número em ponto flutuante %o Lê um número em octal %s Lê um número em cadeia de Caracteres %u Lê um decimal sem sinal %x Lê um hexadecimal %l Lê um inteiro longo %% Busca por um conjunto de caracteres Exemplo 7.1 main() { char a; printf(“Digite um caractere e veja-o em decimal, ”); printf (“ octal e hexadecimal. \n”); scanf(“%c”,&a); printf(“\n%c=%d dec., %o oct. e %x hex. \n”,a,a,a,a); } Execução (faça você mesmo): LINGUAGEM C Algumas funções de E/S 30/103 7.2 Função getche() e getch() A função getche() lê um caractere do teclado sem pressionar <ENTER> e mostra o que foi digitado. Esta função não aceita argumentos e devolve o caracter lido para a função que a chamou. Exemplo 7.2 main() { char ch; printf(“Digite algum caractere:”); ch=getche(); printf(“\n A tecla pressionada eh %c.”, ch); } Execução: Digite algum caractere: a A tecla pressionada eh a. A função getch() lê um caractere do teclado sem pressionar <ENTER> e não mostra o que foi digitado. Esta função não aceita argumentos e devolve o caracter lido para a função que a chamou. Exemplo 7.3 main() { char ch; printf(“Digite algum caractere:”); ch=getch(); printf(“\n A tecla pressionada eh %c.”, ch); } Execução: Digite algum caractere: A tecla pressionada eh a. 7.3 Lendo e escrevendo caracteres A função getchar() lê um caractere do teclado (este comando necessita o pressionamento da tecla <ENTER> após o caractere), e putchar() escreve um caracter na tela. A função getchar() espera até que uma tecla seja pressionada (a qual é mostrada na tela) e devolve o seu valor. A função putchar() escreve seu argumento caractere na tela a partir da posição atual do cursor. Os protótipos para getchar() e putchar() são mostrados aqui: int getchar(void); int putchar(int c); A função getchar() devolve um inteiro, mas o byte de baixa ordem contém o caractere. Além disso, pode-se chamar putchar() com um argumento caractere. A função putchar() devolve o caractere escrito, ou EOF (definida em stdio.h e geralmente é igual a -1), se ocorreu algum erro. A função putchar() não acrescenta um \n a saída. Exemplo 7.4 main() { char ch; printf(“Digite algum caractere:”); ch=getchar(); printf(“\n A tecla pressionada eh ”); putchar(ch); LINGUAGEM C Algumas funções de E/S 31/103 } 7.4 Exercícios 1. Escreva um programa que leia 3 números e mostre a sua média. 2. Escreva um programa que leia 2 números e mostre qual é o maior. 3. Faça um programa que leia dois números e em seguida mostre ,o produto a soma e a subtração entre eles. 4. Faça um programa que leia 3 variáveis a, b e c, coeficientes de uma equação do 2º grau e escreva as duas raízes da equação. 5. Escreva um programa onde o usuário entra com um número qualquer e o programa responda se o número e par ou impar . Se for par emite a mensagem “ O número é par ” ou caso contrário “O número é impar ”. acb, a b x 4 onde 2 2 −=∆∆±−= LINGUAGEM C Comandos Condicionais 32/103 8. Comandos Condicionais Neste capítulo serão vistos os comandos condicionais da linguagem C. 8.1 Comando if Sintaxe: if (<expressão>) <comando>; else <comando>; Onde comando pode ser um único comando, um bloco de comandos ou nada (no caso de comandos vazios). A cláusula else é opcional. Exemplo 8.1 if (x != 0) x = 0; Obs.: Caso haja um else ele pertence ao if mais próximo. Exemplo 8.2 if (a > 5) { if (a < 10) b = 3; } else b = 7; No exemplo 8.2, se o comando if mais interno não fosse separado por chaves, o else pertenceria ao mesmo e não ao mais externo. Exemplo 8.3 /* Programa para adivinhar um numero */ #include “stdio.h” #include “stdlib.h” void main() { int num, tentativa; num = rand(); /*gera numero aleatorio entre 0 e 32767*/ scanf(“%d”, &tentativa); if (tentativa == num) printf(“* * * CERTO * * *”); } Lembre-se que para montar um bloco de comando é somente necessário usar o abre e fecha chaves para marcar início e fim de bloco de comandos respectivamente. 8.2 Comando switch C tem um comando interno de seleção múltipla, switch, que testa sucessivamente o valor de uma expressão contra uma lista de constantes inteiras ou de caractere. Sintaxe: switch (<expressão>) { LINGUAGEM C Comandos Condicionais 33/103 case <valor1> : <seqüência de comandos> break; case <valor2> : <seqüência de comandos> break; … default: <seqüência de comandos> } O valor da expressão é testado, na ordem, contra os valores das constantes especificadas nos comandos case. Quando uma coincidência for encontrada, a seqüência de comandos associada àquele case será executada até que o comando break ou o fim do comando switch seja alcançado. O comando default (opcional) é executado no momento em que não coincidir nenhum valor. Exemplo 8.4 #include <stdio.h> void main() { char opcao; printf(“1. Opcao 1 \n”); printf(“2. Opcao 2 \n”); printf(“3. Opcao 3 \n”); printf(“Opcao:”); opcao=getchar(); switch(opcao) { case ‘1’: printf (“\nVocê escolheu a opcao 1”); break; case ‘2’: printf (“\n Você escolheu a opcao 2”); break; case ‘3’: printf (“\n Você escolheu a opcao 3”); break; default: printf (“\n Nenhuma opcao selecionada”); break; } } 8.3 Exercícios 1. Escrever um programa que leia 3 pares de cordenadas (x,y), que definam 3 pontos e: • Verificar se eles formam um triângulo : não podem estar alinhados e não podem haver pontos sobrepostos. • calcular os lados do triângulo. • classificar o tipo do triângulo: • eqüilátero, isósceles ou escaleno. • acutângulo, obtusângulo ou retângulo 2. Escreva um programa que leia 3 números e os escreve em ordem crescente. 3. Faça um programa que leia um número inteiro, verifique se é positivo ou negativo e escreva uma mensagem apropriada. 4. Faça um programa contendo um menu com as seguintes opções : S - soma P - produto U - subtração D - divisão Q - sair LINGUAGEM C Comandos Condicionais 34/103 O programa deve conter uma função para executar cada tarefa pedida: soma , subtração etc . Quando o usuário teclar ESC o programa deve terminar. LINGUAGEM C Comandos Repititivos ou Laços 35/103 9. Comandos Repititivos ou Laços 9.1 Laço for Comando de repetição condicional com inicialização e incremento. Sintaxe: for(inicialização; condição;incremento) comando; Onde comando é um comando vazio, um comando simples, ou um bloco de comandos. Primeiro é executado a inicialização que consiste em atribuições iniciais. Depois é testada a condição (expressão relacional) de controle do laço. O incremento é executado após a execução do comando ou bloco de comandos. O fim do laço é determinado pelo valor falso que a condição se apresenta. Exemplo 9.1 #include “stdio.h” main() { int x; for(x=1; x<=100; x++) printf(“%d”,x); } No programa acima x é inicializado com 1. Uma vez que x é menor que 100, printf() é executado e x é incrementado em 1 e testado para ver se ainda é menor ou igual a 100. Esse processo se repete até que x fique maior que 100; nesse ponto, o laço termina. Então este programa imprime na tela os números de 1 a 100. 9.2 Laço while Comando de repetição condicional. Sintaxe: while(condição) comando; Onde comando é um comando vazio, uma comando simples, ou um bloco de comandos. Primeiro é testada a condição (expressão relacional) de controle do laço. E a cada execução de comando é repetido o teste de condição. O fim do laço é determinado pelo valor falso que a condição se apresenta. Exemplo 9.1 #include “stdio.h” main() { int cont=0; char ch; while((c=getchar()) != ‘0’) cont++; printf(“Foram lidos %d caracteres”,cont); } LINGUAGEM C Comandos Repititivos ou Laços 36/103 9.3 Laço do-while Ao contrário dos comandos for e while, que testam a condição do laço no começo, o laço do- while verifica a condição ao final do laço. Sintaxe: do <comando> while(condição); Exemplo 9.2 #include <stdio.h> void main() { char opcao; printf(“1. Opcao 1 \n”); printf(“2. Opcao 2 \n”); printf(“3. Opcao 3 \n”); printf(“Opcao:”); do { opcao=getchar(); switch(opcao) { case ‘1’: printf (“\nVocê escolheu a opcao 1”); break; case ‘2’: printf (“\n Você escolheu a opcao 2”); break; case ‘3’: printf (“\n Você escolheu a opcao 3”); break; default: printf (“\n Nenhuma opcao selecionada”); break; }} while (opcao != ‘1’ && opcao != ‘2’ && opcao != ‘3’); } 9.4 Comandos break, continue e exit() O comando break pode ser usado no corpo de qualquer estrutura de laço C. Causa a saída imediata do laço e o controle passa para o próximo comando do programa. Sintaxe: break; O comando continue força a próxima iteração do laço e pula o código que estivar abaixo. Nos laços while e do-while um comando continue faz com que o controle do programa vá diretamente para o teste condicional e depois continue o processo do laço. Sintaxe: continue; Da mesma forma que pode-se sair de um laço, pode-se sair de um programa usando a função exit() da biblioteca padrão. Essa função provoca uma terminação imediata do programa inteiro, forçando um retorno ao sistema operacional. Sintaxe: void exit(int código_de_retorno); LINGUAGEM C Comandos Repititivos ou Laços 37/103 O valor de código_de_retorno é retornado ao processo chamador (sistema operacional). O zero é geralmente usado como um código de retorno que indica uma terminação normal do programa. Exemplo 9.3 #include <stdio.h> void main() { char opcao; printf(“1. Opcao 1 \n”); printf(“2. Opcao 2 \n”); printf(“3. Opcao 3 \n”); printf(“4. Abandonar \n”); printf(“Opcao:”); do { opcao=getchar(); switch(opcao) { case ‘1’: printf (“\nVocê escolheu a opcao 1”); break; case ‘2’: printf (“\n Você escolheu a opcao 2”); break; case ‘3’: printf (“\n Você escolheu a opcao 3”); break; case ‘4’: exit(0); /* retorna ao SO */ }} while (opcao != ‘1’ && opcao != ‘2’ && opcao != ‘3’); } 9.5 Exercícios 1. Escreva um programa para calcular o fatorial de um número lido. 2. Escreva um programa para ler um caracter de comparação e vários caracteres de entrada finalizados por ‘0’ e contar o número de vezes que o caracter de comparação apareceu. 3. Escrever um programa que mostre os números primos entre 1 e 100. 4. Escreva um programa que leia um número não determinado de valores positivos, e mostre a soma e média dos respectivos números ao ser lido um valor negativo. 5. Faça um programa que leia um número (inteiro) e escreva todos os seus divisores. 6. Faça um programa que leia um número de no máximo 3 dígitos e escreva-o na tela. 7. Faça um programa que receba como entrada uma quantia em dinheiro e mostre na tela a quantidade de notas de 5, 10, 50 e 100 são necessárias para representar o valor. O programa deve contabilizar a partir das notas de 100. 8. Suponha um número N qualquer: se N é par então N agora é N / 2; se N é ímpar N agora é 3*N + 1. Assim para N = 3 calculamos a seguinte tabela : 3 � 10 4 � 2 10 � 5 2 � 1 5 � 16 1 � 4 16 � 8 4 � 2 8 � 4 2 � 1 Observe que a partir de sete iterações a seqüência 4 2 1 começa a se repetir . Faça um programa que calcule para um dado N o número de iterações até se chegar ao primeiro 1 . LINGUAGEM C Comandos Repititivos ou Laços 38/103 9. Faça um programa que imprima os elementos de uma PA e o somatório da mesma dados : primeiro termo , numero de termos e razão 10. Faça um programa que imprima um elemento da seqüência de Fibonacci , dado o numero do elemento. 11. Faça um programa onde o usuário entra com um número decimal e o mesmo calcula e imprime o número no sistema binário . 12. Faça um programa onde o usuário entra com dois números A e B o programa devolve como resultado A elevado a B . 13. Escreva um programa que solicite ao usuário três números inteiros a,b,c onde a é maior que 1 . Seu programa deve somar todos os inteiros entre b e c divisíveis por a . LINGUAGEM C Funções 39/103 10. Funções A forma geral de uma função é: Sintaxe: tipo_função nome_função (lista_de_parâmetros) declaração_parâmetros { corpo_função; } Exemplo 10.1 int soma(int x, int y) { ... } ou int soma(x, y) int x,y; { ... } As funções retornam um valor (do tipo indicado em tipo_função). O valor retornado pela função é dado pelo comando return (o valor retornado pode ou não ser utilizado). Existem dois tipos de passagem de argumentos: por valor e por referência. A segunda é realizada através de apontadores. Exemplo 10.2 int pot(x,n) /* x elevado na n potência */ int x,n; { int p; for(p=1;n>0;n--) p *= x; return p; } No exemplo 11.2, os argumentos foram passados por valor e a função retorna um valor do tipo inteiro. A chamada seria: a = pot(10,2); No exemplo 11.3, nenhum valor é retornado (por isso usa-se o tipo void) mas é realizado uma troca dos valores das variáveis, necessitando de uma passagem de parâmetros por referência. Exemplo 10.3 void troca(a,b) /* troca os valores das variáveis */ int *a, *b; /* usa-se ponteiros para referenciar */ { /* o conteúdo do endereço da variável */ int aux; aux = *a; *a = *b; *b = aux; } LINGUAGEM C Funções 40/103 A chamada para esta função seria: int x=1,y=2; troca(&x,&y); Na passagem de parâmetros por referência é passado explicitamente o endereço da variável com o uso do operador &. Quando o argumento for uma matriz automaticamente será passado o endereço da matriz para a função. A linguagem C aceita chamadas recursivas de funções. 10.1 Localização das funções Existem basicamente duas posições possíveis para escrevermos o corpo de uma função: ou antes ou depois do programa principal. Podemos ainda escrever uma função no mesmo arquivo do programa principal ou em arquivo separado. 10.1.1 Corpo da função antes do programa principal (no mesmo arquivo) Quando escrevemos a definição de uma função antes do programa principal e no mesmo arquivo deste, nenhuma outra instrução é necessária. Exemplo 10.4 float media2(float a, float b){ // função float med; med = (a + b) / 2.0; return(med); } void main(){ // programa principal float num_1, num_2, med; puts(”Digite dois números:”); scanf(”%f %f”, &num_1, &num_2); med = media2(num_1, num_2); // chamada da função printf(”\nA media destes números e´ %f”, med); } 10.1.2 Corpo da função depois do programa principal (no mesmo arquivo) Quando escrevemos a definição de uma função depois do programa principal e no mesmo arquivo deste, devemos incluir um protótipo da função chamada. Um protótipo é uma instrução que define o nome da função, seu tipo de retorno e a quantidade e o tipo dos argumentos da função. O protótipo de uma função indica ao compilador quais são as funções usadas no programa principal os tipo. A sintaxe geral para isto é a seguinte: Sintaxe: void main(){ // programa principal tipo nomef(...); // protótipo da função ... var = nomef(...) // chamada a função ... } LINGUAGEM C Funções 41/103 tipo nomef(...){ // definição da função [corpo de função] } Exemplo 10.5 void main(){ // programa principal float media2(float,float); // protótipo de media2() float num_1, num_2, med; puts(”Digite dois números:”); scanf(”%f %f”, &num_1, &num_2); med = media2(num_1, num_2); // chamada a função printf(”\nA media destes números e´ %f”, med); } float media2(float a, float b){ // função media2() float med; med = (a + b) / 2.0; return(med); } Observe que o protótipo de uma função nada mais é que a declaração da função sem o seu corpo. Observe ainda que na lista de argumentos do protótipo podem ser escritos apenas os tipos dos argumentos. 10.1.3 Corpo da função escrito em arquivo separado Em C, como em muitas outras linguagens, é permitido que o usuário crie uma função em um arquivo e um programa que a chame em outro arquivo distinto. Esta facilidade permite a criação de bibliotecas de usuário: um conjunto de arquivos contendo funções escritas pelo usuário. Esta possibilidade é uma grande vantagem utilizada em larga escala por programadores profissionais. Quando escrevemos a definição de uma função em arquivo separado do programa principal devemos incluir este arquivo no conjunto de arquivos de compilação do programa principal. Esta inclusão é feita com a diretiva #include. Esta diretiva, vista nas seções 2.4.2 e 3.7.1, instrui o compilador para incluir na compilação do programa outros arquivos que contem a definição das funções de usuário e de biblioteca. Sintaxe: #include ”path” // inclusão da função void main(){ // programa principal ... var = nomef(...) // chamada a função ... } Na diretiva #include, indicamos entre aspas duplas o caminho de localização do arquivo onde está definida a função chamada. Exemplo 10.6 #include ”c:\tc\userbib\stat.h” // inclusão da função void main(){ // programa principal float num_1, num_2, med; puts(”Digite dois números:”); scanf(”%f %f”, &num_1, &num_2); med = media2(num_1, num_2); // chamada a função LINGUAGEM C Funções 42/103 printf(”\nA media destes números e´ %f”, med); } 10.2 Argumentos para função main() A função main() aceita argumentos para a passagem de parâmetros realizada através da chamada do programa. Os dois argumentos são: argc: contador de argumentos; argv: vetor de argumentos (vetor de apontadores para strings). Sintaxe: main(int argc, char *argv[]) É importante lembrar que Exemplo 10.7 #include “stdio.h” void main(int argc, char *argv[]) { int cont; printf(“Foram encontrados %d argumentos \n”,argc -1); for (cont=1;cont < argc;cont++) printf(“Argumento %d: %s \n”, cont, argv[cont]); } O primeiro argumento (argv[0]) é o nome do programa. 10.3 Protótipo de funções O padrão ANSI C expandiu a declaração tradicional de função, permitindo que a quantidade e os tipos dos argumentos das funções sejam declarados. A definição expandida é chamada protótipo de função. Protótipos de funções não faziam parte da linguagem C original. Protótipos permitem que C forneça uma verificação mais forte dos tipos. Protótipos de funções ajudam a detectar erros antes que eles ocorram. É verificado número de parâmetros, compatibilidade de tipos, entre outras. Existem três tipos de declaração de protótipos: Sintaxe Exemplo tipo_função nome_função(); int pot(); tipo_função nome_função(lista_tipo_argumentos); int pot(int,int); tipo_função nome_função(lista_tipo_nome_argumentos); int pot(int x, int y); 10.4 Retorno de Ponteiros Ponteiros para variáveis não são variáveis e tampouco inteiros sem sinal. São endereço na memória de um certo tipo de dado. A forma geral é: Sintaxe: tipo_função *nome_função(lista_de_argumentos); LINGUAGEM C Funções 43/103 10.5 Números de parâmetros variável Em C, pode-se especificar uma função que possui a quantidade e os tipos de parâmetros variáveis. O exemplo mais comum é printf(). Para informar ao compilador que um número desconhecido de parâmetros será passado para uma função, deve-se terminar a declaração dos seus parâmetros usando três pontos. Por exemplo, essa declaração especifica que func() terá ao menos dois parâmetros inteiros e um número desconhecido (incluindo 0) de parâmetros após eles. func(int a, int b, ...); Essa forma de declaração também é usada por um protótipo de função. Qualquer função que usa um número variável de argumentos deve ter pelo menos um argumento verdadeiro. Por exemplo, isso está incorreto: func(...); Para poder utilizar esta característica tem-se que utilizar as funções descritas na próxima seção. 10.5.1 Funções para número variável de argumentos Para este tipo de aplicação é necessário o uso de três macros definidas em STDARG.H: va_arq(), va_start() e va_end(). Sintaxe: void va_arg(va_list argptr, type); type va_start(va_list argptr, last_parm); void va_end(va_list argptr); O tipo va_list é definido em STDARG.H. O procedimento geral para criar uma função que pode receber um número variável de argumentos é o seguinte: a função deve ter pelo menos um parâmetro conhecido, podendo, porém, ter mais anterior à lista variável de parâmetros. O parâmetro conhecido, mais à direita, é last_parm. Antes que qualquer dos parâmetros de comprimento variável possa ser acessado, o argumento ponteiro argptr deve ser inicializado através de uma chamada à va_start(). Em seguida, parâmetros são devolvidos via chamadas à va_arg(), com type sendo o tipo do próximo parâmetro. Finalmente, após todos os parâmetros terem sido lidos e antes de retornar a função, deve ser feita uma chamada à va_end() para garantir que a pilha seja corretamente restaurada. Se va_end() não for chamada, é muito provável que ocorra um crash do programa. No exemplo abaixo, utiliza-se sum_series() para devolver a soma de uma série de números. O primeiro argumento contém o número de argumentos que se sucedem. #include “stdio.h” #include “stdarg.h” double sum_series(int num, ...); void main() { double d, sum_series(); d = sum_series(5, 0.5, 0.25, 0.125, 0.0625, 0.03125); printf(“soma da serie: %f \n”,d); } double sum_series(int num, ...) LINGUAGEM C Funções 44/103 { double sum=0.0, t; va_list argptr; va_start(argptr,num); /* inicializa argptr */ for (;num;num--) /* soma a serie */ { t = va_arg(argptr,double); sum += t; } va_end(argptr); /* finaliza a lista de argumentos */ return sum; } 10.6 Classes de Armazenamento São quatro as classes de armazenamento de variáveis C: • auto (automáticas) • extern (externas) • static (estáticas) • register (em registradores) 10.6.1 Classe de Armazenamento - auto As variáveis declaradas nos exemplos anteriores só podem ser acessadas somente às funções onde estão declaradas. Tais variáveis são chamadas locais ou automáticas, são criadas quando a função é chamada e destruídas quando a função ou o bloco de código termina a sua execução. As variáveis declaradas dentro de uma função são automáticas por padrão. A classe de variáveis automáticas pode ser explicitada usando-se a palavra auto. O código main() { auto int x; ... } é equivalente a main() { int x; ... } 10.6.2 Classe de Armazenamento - extern Toda variável declarada fora de qualquer função têm a classe de armazenamento extern. Como pode-se somente uma única vez declarar uma variável global, ao usar diversos arquivos para um mesmo programa (por ser grande, por exemplo) deve-se utilizar a declaração extern nos outros arquivos onde a variável é utilizada. Se não proceder assim, o compilador acusará um erro de duplicidade de variável. Exemplo 10.8 Arquivo 1 int x,y; char ch; main() { ... } func1() { x = 123; } LINGUAGEM C Funções 45/103 Arquivo 2 extern int x,y; extern char ch; func22() { x = y / 10; } func23() { y = 10; } No arquivo 2, a lista de variáveis globais foi copiada do arquivo 1 e o especificador extern foi adicionado às declarações. O especificador extern diz ao compilador que os tipos de variável e nomes que o seguem foram declarados em outro lugar. Isto é, o compilador não reserva um espaço de memória para essas variáveis declaradas com o especificador extern na certeza de estarem declaradas em outro módulo. 10.6.3 Classe de Armazenamento - static Dentro de sua própria função ou arquivo, variáveis static são variáveis permanentes. Ao contrário das variáveis globais, elas não são reconhecidas fora de sua função ou arquivo, mas mantêm seus valores entre chamadas. O especificador static tem efeitos diferentes em variáveis locais e em variáveis globais. 10.6.3.1 Variáveis Locais static Quando o modificador static é aplicado a uma variável local, o compilador cria armazenamento permanente para ela quase da mesma forma como cria armazenamento para uma variável global. Em termos simples, uma variável local static é uma variável local que retém seu valor entre chamadas. Mas ela só é reconhecida apenas no bloco em que está declarada. 10.6.3.2 Variáveis Globais static Quando o modificador static é aplicado a uma variável global, o compilador cria uma variável que é reconhecida apenas no arquivo na qual a mesma foi declarada. 10.6.4 Classe de Armazenamento - register A classe de armazenamento register indica que a variável associada deve ser guardada fisicamente numa memória de acesso muito mais rápido, chamada registrador. No caso do IBM-PC pode ser colocado o tipo int e char associado a register pois o registrador tem apenas 16 bits. Basicamente, variáveis register são usadas para aumentar a velocidade de processamento. Por exemplo, variáveis de uso freqüente como variáveis de laços e argumentos de funções. 10.7 Diretiva #define A diretiva #define pode ser usada para definir constantes simbólicas com nomes apropriados. Por exemplo, a constante PI pode ser definida com o valor 3.14159. #define PI 3.14159 Só pode ser escrito um comando destes por linha, e não há ponto-e-vírgula após qualquer diretiva do pré-processador. Esta diretiva é usada para definir macros com argumentos. LINGUAGEM C Funções 46/103 #define AREA(x) (4*PI*x*x) A declaração acima define a função AREA() a qual calcula a área de uma esfera. A vantagem desta declaração é não tipagem do argumento x. Não deve haver espaços entre o nome da macro e seus identificadores. 10.8 Funções Recursivas Uma função é dita recursiva quando se é definida dentro dela mesma. Isto é, uma função e´ recursiva quando dentro dela está presente uma instrução de chamada a ela própria. Exemplo 10.9 // imprime uma frase invertida . Usa recursao #include <stdio.h> #include <conio.h> void inverte( ) void main( ) { clrscr( ); inverte( ); getch(); } void inverte ( ) { char ch ; if ((ch=getche( )) != ‘\r’ ) inverte( ); scanf(“%c”,ch) } 10.9 Exercícios 1. Escreva um programa que receba como parâmetro um índice (float). Após, ler uma sequência de números (a qual termina por 0) e exibir o seu valor multiplicado pelo índice. A função que transforma uma string em um float é atof(char *x). 2. Escreva uma função que receba um caracter como argumento e que retorne a letra maiúscula se a mesma for minúscula. funções: islower(int ch), toupper(int ch). 3. Escreva uma função que calcule o fatorial de forma recursiva. 4. Implementar as seguintes funções: � strlen() � strcat() � strcpy() LINGUAGEM C Vetores e Matrizes 47/103 11. Vetores e Matrizes Vetores e matrizes são estruturas de dados usada para representar uma certa quantidade de variáveis de valores homogêneos. Em C, estas estruturas precisam ser declaradas, como quaisquer outras variáveis, para que o compilador conheça o tipo do vetor ou da matriz e reserve espaço de memória suficiente para armazená-lo. O que diferencia a declaração de uma matriz ou vetor da declaração de qualquer variável é a parte que segue o nome, isto é, os pares de colchetes ([ e ]) que envolvam um número inteiro, que indica ao compilador o tamanho da matriz. int notas[5]; A declaração acima aloca um intervalo de memória com nome notas para armazenar 5 elementos do tipo int. Por definição um vetor ou matriz é composto por elementos de um único tipo. Para a declaração de mais de uma dimensão em C é necessário o número de par de colchetes ser igual ao número de dimensões. Isto é, se a matriz for de duas dimensões teremos uma declaração assim: int notas[5][5]; que declara uma matriz bi-dimensional; para três dimensões assim int notas[5][5][5]; que declara uma matriz tridimensional e assim por diante. O limite de índices é definido pelo compilador ou pela memória. Os elementos do vetor ou da matriz são sempre enumerados por índices iniciados por 0 (zero). Exemplo 11.1 for (i=0;i<5;i++) { printf(“Digite a nota do aluno %d:”,i); scanf(“%d”, ¬as[i]); } Obs.: A linguagem C não realiza verificação de limites em matrizes!!! 11.1 Inicialização de Vetores e Matrizes Como o C permite a inicialização de variáveis básicas, também é permitido a inicialização de vetores e matrizes. Exemplo 11.2 int TAB[5]={1,5,10,15,20} int MAT[5][6]={ { 1, 0, 1, 0, 1, 0 }, { 0, 1, 0, 1, 0, 1 }, { 1, 0, 1, 0, 1, 0 }, { 0, 1, 0, 1, 0, 1 }, { 1, 0, 1, 0, 1, 0 } }; Obs.: Quando houver uma inicialização de um vetor ou uma matriz na declaração, pode-se suprimir sempre o valor do primeiro colchete, isto é, pelo número de argumentos ele assume o número do primeiro colchete. LINGUAGEM C Vetores e Matrizes 48/103 Exemplo 11.3 int TAB[]={1,5,10,15,20} int MAT[][6]={ { 1, 0, 1, 0, 1, 0 }, { 0, 1, 0, 1, 0, 1 }, { 1, 0, 1, 0, 1, 0 }, { 0, 1, 0, 1, 0, 1 }, { 1, 0, 1, 0, 1, 0 } }; 11.2 Matrizes e Vetores como argumento de Funções O C permite que passe matrizes ou vetores como argumento de uma função. Mas há uma peculiaridade na passagem de parâmetros: a forma como ela é passada. O nome de uma matriz ou vetor desacompanhado de colchetes é equivalente ao endereço da matriz ou do vetor, isto é, passagem por referência. Quando o nome estiver acompanhado de um indexador é passado como argumento o conteúdo daquela posição da matriz ou do vetor, isto é, passagem por valor. 11.3 Limites Na linguagem C, devemos ter cuidado com os limites de um vetor. Embora na sua declaração, tenhamos definido o tamanho de um vetor, o C não faz nenhum teste de verificação de acesso a um elemento dentro do vetor ou não. Por exemplo se declaramos um vetor como int valor[5], teoricamente só tem sentido usarmos os elementos valor[0], ..., valor[4]. Porém, o C não acusa erro se usarmos valor[12] em algum lugar do programa. Estes testes de limite devem ser feitos logicamente dentro do programa. Este fato se deve a maneira como o C trata vetores. A memória do microcomputador é um espaço (físico) particionado em porções de 1 byte. Se declaramos um vetor como int vet[3], estamos reservando 6 bytes (3 segmentos de 2 bytes) de memória para armazenar os seus elementos. O primeiro segmento será reservado para vet[0], o segundo segmento para vet[1] e o terceiro segmento para vet[2]. O segmento inicial é chamado de segmento base, de modo que vet[0] será localizado no segmento base. Quando acessamos o elemento vet[i], o processador acessa o segmento localizado em base+i. Se i for igual a 2, estamos acessando o segmento base+2 ou vet[2](o ultimo segmento reservado para o vetor). Porém, se i for igual a 7, estamos a acessando segmento base+7 que não foi reservado para os elementos do vetor e que provavelmente está sendo usado por uma outra variável ou contém informação espúria (lixo). Observe que acessar um segmento fora do espaço destinado a um vetor pode destruir informações reservadas de outras variáveis. Estes erros são difíceis de detectar pois o compilador não gera nenhuma mensagem de erro... A solução mais adequada é sempre avaliar os limites de um vetor antes de manipulá-lo. A princípio este fato poderia parecer um defeito da linguagem, mas na verdade trata-se de um recurso muito poderoso do C. Poder manipular sem restrições todos os segmentos de memória é uma flexibilidade apreciada pelos programadores. LINGUAGEM C Vetores e Matrizes 49/103 11.4 Strings Em C não existe um tipo de dados string como na linguagem "Pascal". Ao contrário, as string são implementadas como cadeia de caracteres, terminados pelo caracter null (‘\0’). O caracter null serve como uma marca de fim de string para as funções que lidam com strings. Como em qualquer vetor, os caracteres da string podem ser individualmente acessados. A declaração: char nome[15]; reserva um espaço de memória para uma string de tamanho 14, pois o décimo-quinto byte é para o caracter null. A manipulação de strings pode ser feita através de funções ou elemento a elemento como se fosse um vetor. Exemplo 11.4 char str[2]; str[0] = ‘U’; str[1] = ‘/0’; /* Caracter NULL para finalizar string */ Como todo vetor as strings podem ser inicializadas, como mostram o exemplo 10.5: Exemplo 11.5 char nome[]={‘C’,’a’,’r’,’l’,’o’,’s’,’\0’}; char nome[]=“Carlos”; char nomes[][8]={“Eduardo”, “Andre”, “Adriana”, “Carla”, “Jose”}; 11.4.1 Leitura de Strings Para ler uma string pode-se utilizar a função scanf() ou a função gets() da biblioteca C para receber a string. 11.4.1.1 Função scanf() A função scanf() é bastante limitada para a leitura de strings. Pois não pode haver espaços em branco na string lida. O que vier após o espaço em branco é eliminado. Exemplo 11.6 #include <stdio.h> main() { char nome[15]; printf(“Digite seu nome:”); scanf(“%s”,&nome); printf(“Saudacoes %s”,nome); } Execução Digite seu nome: Carlos Alberto LINGUAGEM C Vetores e Matrizes 50/103 Saudacoes, Carlos 11.4.1.2 Função gets() A função gets() é bastante conveniente para a leitura de strings. O seu propósito é unicamente ler uma string da sua entrada padrão que por default é o teclado. Visto que uma string não tem um tamanho pré-determinado, gets() lê caracteres até encontrar o de nova linha (‘\n’) que é gerado ao pressionar a tecla [Enter]. Todos os caracteres anteriores ao ‘\n’ são armazenados na string e é então incluído o caractere ‘\0’. Caracteres brancos como espaços e tabulações são aceitáveis como parte da string. Exemplo 11.7 #include <stdio.h> main() { char nome[15]; printf(“Digite seu nome:”); gets(nome); printf(“Saudacoes %s”,nome); } Execução Digite seu nome: Carlos Alberto Saudacoes, Carlos Alberto 11.4.2 Escrita de Strings Para escrever uma string pode-se utilizar a função printf() ou a função puts() da biblioteca C. 11.4.2.1 Função printf() A função printf() ao imprimir a string não realiza uma nova linha, como é definido na sua implementação. Em compensação é uma função que pode ser usada para impressão de mais que uma string por linha. 11.4.2.2 Função puts() A função puts() é o complementa de gets(). O seu propósito é unicamente imprimir a string apontada pelo seu argumento. O endereço desta string deve ser mandado para puts() como argumento. O próximo exemplo ilustra algumas das muitas possibilidades de seu uso. /* mostra o uso de putc() */ #include <stdio.h> main() { char nome[81]; puts(“Digite seu nome:”); gets(nome); puts(“Saudacoes, ”); puts(nome); puts(“puts() pula de linha sozinha”); LINGUAGEM C Vetores e Matrizes 51/103 puts(&nome[4]); } Execução Digite seu nome: Carlos Alberto Saudacoes, Carlos Alberto puts() pula de linha sozinha los Alberto A função puts() reconhece ‘/0’como fim de string. Cada impressão de puts(), ele acaba com um caracter de nova linha. Utilizando a função printf() o mesmo efeito seria: printf(“%s\n”,nome); puts(nome); 11.4.3 Funções de Manipulação de Strings Como strings são implementadas como vetores, os operadores usuais não podem ser aplicados sobre este tipo de dado. Por exemplo: comparação atribuição não são suportados por este tipo de dado. As funções descritas nas próximas seções utilizam o cabeçalho padrão STRING.H. 11.4.3.1 Função strlen() Devolve o comprimento da string terminada por um nulo apontada por str. O nulo não é contado. Sintaxe: size_t strlen(char *str); Exemplo 11.8 printf(“%d”,strlen(“ola”)); /* O resultado é 3 */ 11.4.3.2 Função strcat() Concatena uma cópia de str2 em str1 e termina str1 com um nulo. Como não existe verificação de limites, é de inteira responsabilidade do usuário que o tamanho de str1 seja suficientemente grande para armazenar seu conteúdo original e o de str2. Sintaxe: char *strcat(char *str1, const char *str2); Exemplo 11.9 printf(“%s”,strcat(“ola”, “ mundo”)); /* O resultado é ola mundo */ LINGUAGEM C Vetores e Matrizes 52/103 11.4.3.3 Função strcmp() Compara lexicograficamente duas strings e devolve um inteiro baseado no resultado como mostrado aqui: Valor Significado Menor que zero str1 é menor que str2 Zero str1 é igual a str2 Maior que zero str1 é maior que str2 Sintaxe: int strcmp(const char *str1, const char *str2); 11.4.3.4 Função strcpy() Copia o conteúdo de str2 em str1. str2 deve ser um ponteiro para uma string terminada com um nulo. A função strcpy() devolve um ponteiro para str1. Sintaxe: char *strcpy(char *str1, const char *str2); Exemplo 11.10 char str[80]; strcpy(str, “mundo”); /* copia mundo na string str */ 11.4.3.5 Função strchr() Devolve um ponteiro à primeira ocorrência do byte menos significativo de ch na string apontada por str. Se não for encontrada nenhuma coincidência, será devolvido um ponteiro nulo. Sintaxe: char *strchr(const char *str, int ch); 11.4.3.6 Função strstr() Devolve um ponteiro à primeira ocorrência da string apontada por str2 na string apontada por str1. Se não for encontrada nenhuma coincidência, será devolvido um ponteiro nulo. Sintaxe: char *strstr(const char *str1, const char *str2); 11.4.4 Exemplo Geral O programa abaixo exemplifica o uso das funções descritas anteriormente. Exemplo 11.11 #include “stdio.h” #include “string.h” void main() LINGUAGEM C Vetores e Matrizes 53/103 { char s1[80], s2[80]; gets(s1); gets(s2); printf(“comprimento: %d %d \n”, strlen(s1), strlen(s2)); if (!strcmp(s1,s2)) printf(“As strings sao iguais\n”); strcat(s1,s2); printf(“%s\n”,s1); strcpy(s1, “Isso e um teste \n”); printf(s1); if (strchr(“alo”,’o’)) printf(“o esta em alo \n”); if (strstr(“ola aqui”, “ola”)) printf(“ola encontrado”); } Ao rodar este programa e entrar com as strings “ola” e “ola”, a saída será comprimentos: 3 3 As strings são iguais aloalo Isso e um teste o esta em alo ola encontrado 11.5 Exercícios 1. Faça um programa que leia 10 valores inteiros e escreva-os na ordem inversa a que foram lidos. 2. Faça um programa que leia 10 valores inteiros, calcule sua média e escreva todos os valores que estão abaixo da média. 3. Calcular os números primos entre 0 e 100 (inclusive) utilizando o crivo de Eritóstenes: � Usar um vetor de 101 inteiros; � zerar o vetor; � repetir de 2 a 100 � para cada número: somar 1 a todos os contadores dos seus múltiplos. � ao final os números que possuem 0 no vetor são primos. 4. Escreva um programa que leia um vetor de 100 posições e mostre-o em ordem crescente e decrescente. 5. Faça um programa que leia uma matriz M[10][10] e some todos os elementos abaixo da diagonal principal. 6. Faça um programa que leia 3 nomes (até 30 letras) e os escreva em ordem alfabética. 7. Escreva um programa que leia um número indeterminado de caracteres e conta o número de vezes que cada letra (A-Z) aparece: � Desconsiderar se a letra é maiúscula ou minúscula; � Termina com o caracter ´0´. 8. Faça um programa que leia uma matriz M[4][4] e mostra a sua transposta. 9. Escreva um programa cuja execução se segue abaixo : digite uma frase : LINGUAGEM C Vetores e Matrizes 54/103 carlos <enter> digite uma letra dessa frase : r <enter> rlos 10. Escreva uma função que realize a multiplicação de matrizes. 11. Escrever a função atoi() (recebe uma string como parâmetro e transforma em um inteiro). 12. Faça um programa que use a função (definida por você) : char *concat( char *s1 , char * s2) . A função retorna um ponteiro que é a concatenação de s2 com s1 . Exemplo de chamada : char mat1[80]=”casa ” ; char mat2[80]=”grande”; char *p; p = concat(mat1,mat2); printf( “%s”,p); 13. Escreva uma função que receba um vetor e o tamanho do mesmo e troque os elementos (1º com o último, o 2º com o penúltimo e assim por diante). LINGUAGEM C Ponteiros 55/103 12. Ponteiros Para uma boa utilização dos ponteiros deve-se compreender corretamente o seu uso. Existem três razões para isso: primeiro, ponteiros fornecem os meios pelos quais as funções podem modificar seus argumentos; segundo, eles são usados para suportar as rotinas de alocação dinâmica de C, e terceiro, o uso de ponteiros para aumentar a eficiência de certas rotinas. Por ser um dos aspectos mais poderosos da linguagem também são os mais perigosos. Por erros no uso de ponteiros (como a não inicialização de ponteiros - ponteiros selvagens) podem provocar quebra do sistema. Por definição, um ponteiro é uma variável que contém um endereço de memória. Esse endereço é normalmente uma posição de uma outra variável na memória. Uma declaração de ponteiros consiste no tipo base, um "*" e o nome da variável. A forma geral é: tipo *nome; onde tipo é qualquer tipo válido em C e nome é o nome da variável ponteiro. O tipo base do ponteiro define que tipo de variáveis o ponteiro pode apontar. Basicamente, qualquer tipo ponteiro pode apontar para qualquer lugar, na memória. Mas, para a aritmética de ponteiros é feita através do tipo base. Os operadores utilizados são * e &, como já foi explicado na seção 6.6. 12.1 Expressões com Ponteiros Nesta seção serão analisados alguns aspectos especiais de expressões com ponteiros. 12.1.1 Atribuição de Ponteiros Como é o caso com qualquer variável, um ponteiro pode ser usado no lado direito de um comando de atribuição para passar seu valor para um outro ponteiro. Exemplo 12.1 #include “stdio.h” void main() { int x; int *p1,*p2; /* declaração do ponteiro p1 e p2 com o tipo base int. */ p1 = &x; p2 = p1; printf(“%p”,p2); /* escreve o endereço de x, não seu valor */ } Agora, tanto p1 quanto p2 apontam para x. O endereço de x é mostrado usando o modificador de formato de printf() %p, que faz com que printf() apresente um endereço no formato usado pelo computador hospedeiro. LINGUAGEM C Ponteiros 56/103 12.1.2 Aritmética de Ponteiros Existem apenas duas operações aritméticas que podem ser usadas com ponteiros: adição e subtração. Os operadores permitidos no caso seriam: +, -, ++, --. O incremento é sempre realizado do tamanho básico de armazenamento do tipo base. Isto é, se o tipo base for um inteiro e incrementarmos em uma unidade, o apontador apontará para o próximo inteiro (no caso do inteiro ocupar 2 bytes o incremento será de dois bytes), no caso de um caracter (char) será de um byte. Exemplo 12.1 int *ptri=3000; char *ptrc=4000; float *ptrf=4000; ptri++; /* ptri apontará para o endereço 3002 */ ptrc++; /* ptrc apontará para o endereço 4001 */ ptrf++; /* ptrf apontará para o endereço 5004 */ ptri = ptri - 10; /* ptri apontará para o endereço 2982 */ ptrc = ptrc - 10; /* ptrc apontará para o endereço 3991 */ ptrf = ptrf - 10; /* ptrf apontará para o endereço 4964 */ Além de adição e subtração entre um ponteiro e um inteiro, nenhuma outra operação aritmética pode ser efetuada com ponteiros. Isto é, não pode multiplicar ou dividir ponteiros; não pode aplicar os operadores de deslocamento e de mascaramento bit a bit com ponteiros e não pode adicionar ou subtrair o tipo float ou o tipo double a ponteiros. Lembre-se! Não altera-se o valor de um ponteiro constante (ponteiro para um tipo básico - int, float double, entre outros), somente de um ponteiro variável (ponteiro de estruturas complexas - vetores, matrizes, strings, entre outros). 12.2 Inicialização de Ponteiros Após um ponteiro ser declarado, mas antes que lhe seja atribuído um valor, ele contém um valor desconhecido. Ao usar este ponteiro antes de inicializar, provavelmente provocará uma falha do programa ou até do sistema operacional. Como um ponteiro nulo é assumido como sendo não usado, pode-se utilizar o ponteiro nulo para fazer rotinas fáceis de codificar e mais eficientes. Por exemplo, pode-se utilizar um ponteiro nulo para marcar o fim de uma matriz de ponteiros. Uma rotina que acessa essa matriz sabe que chegará ao final ao encontrar o valor nulo. A função search(), mostrada no exemplo 13.11, ilustra esse tipo de abordagem. Exemplo 12.1 /* procura um nome */ search(char *p[], char *name) { register int t; for (t=0;p[t];++t) if(!strcmp(p[t],name)) return t; retrun -1; /* não encontrado */ } O laço for dentro de search() é executado até que seja encontrada uma coincidência ou um ponteiro nulo. Como o final da matriz é marcado com um ponteiro nulo, a condição de controle do laço falha quando ele é atingido. Uma outra utilização de inicialização de ponteiros é a inicialização de strings. Isto pode ser levado como uma variação no tema de inicialização usado na variável argv. LINGUAGEM C Ponteiros 57/103 Exemplo 12.2 char *p= “alo mundo \n”; O ponteiro p (exemplo 13.12) não é uma matriz, mas como o compilador C cria uma tabela de strings, a constante string é colocada em tal tabela sendo que a mesma pode ser utilizada em todo o programa como se fosse uma string comum (exemplo 13.13). Por isso, inicializar uma matriz de strings usando ponteiros aloca menos memória que a inicialização através de matriz. Exemplo 12.3 #include “stdio.h” #include “string.h” char *p=“alo mundo”; void main() { register int t; printf(p); for (t=strlen(p) - 1; t > -1; t--) printf(“%c”,p[t]); } 12.2.1 Comparação de Ponteiros É possível comparar dois ponteiros em uma expressão relacional. Exemplo 12.2 if (p<q) printf(“p aponta para uma memória mais baixa que q /n”); Geralmente, a utilização de comparação entre ponteiros é quando os mesmos apontam para um objeto comum. Exemplo disto é a pilha, onde é verificado se o ponteiro de início e fim da pilha estão apontando para a mesma posição de memória, significando pilha vazia. 12.3 Ponteiros e Matrizes Existe uma estreita relação entre matrizes e ponteiros. Pois C fornece dois métodos para acessar elementos de matrizes: aritmética de ponteiros e indexação de matrizes. Aritmética de ponteiros pode ser mais rápida que indexação de matrizes. Normalmente utiliza-se ponteiros para acessar elementos de matrizes devido a velocidade de acesso. Exemplo 12.3 char str[80], *p1; p1 = str; Para acessar a string str pode-se utilizar estes dois mecanismos str[4] /* indexação de matrizes */ ou *(p1 + 4) /* aritmética de ponteiros */ Os dois comandos devolvem o quinto elemento. *(matriz + índice) é o mesmo que matriz[índice]. LINGUAGEM C Ponteiros 58/103 Para uma melhor compreensão ou facilidade de programação as funções de indexação trabalham com ponteiros (como mostra o exemplo 13.5 a implementação da função puts()). Exemplo 12.4 /* Indexa s como uma matriz */ void put(char *s) { register int t; for (t=0;s[t]; ++t) putchar(s[t]); } /* Acessa s como um ponteiro */ void put(char *s) { while (*s) putchar(*s++); } No caso da passagem de parâmetros é possível tratar uma matriz como se fosse um ponteiro. Exemplo 12.5 #include “stdlib.h” #include “stdio.h” #include “string.h” void le_tab(int *p) { register int i; for(i=0; i<20; i++) scanf(“%d”,(p+i)); } void mostra_tab(int *p) { register int i; for(i=0; i<20; i++) printf(“%d”,*(p+i)); } void main(void) { int mat[20]; le_tab(mat); mostra_tab(mat); } 12.3.1 Matrizes de Ponteiros Ponteiros podem ser organizados em matrizes como qualquer outro tipo de dado. A declaração de uma matriz de ponteiros int, de tamanho 10, é int *x[10]; Para atribuir o endereço de uma variável inteira, chamada var, ao terceiro elemento da matriz de ponteiros, deve-se escrever x[2] = &var; para encontrar o valor de var, escreve-se *x[2]; LINGUAGEM C Ponteiros 59/103 Se for necessário passar uma matriz de ponteiros para uma função, pode ser usado o mesmo método que é utilizado para passar outras matrizes - simplesmente chama-se a função com o nome da matriz sem qualquer índice (como mostra o exemplo 13.7). Exemplo 12.6 void display_array(int *q[]) { register int t; for (t=0; t<10; t++) printf(“%d”,*q[t]); } Lembre-se de que q não é um ponteiro para inteiros; q é um ponteiro para uma matriz de ponteiros para inteiros. Portanto, é necessário declarar o parâmetro q como uma matriz de ponteiros para inteiros, como é mostrado no exemplo 13.7. Isto é, não é uma passagem de parâmetros por referência por dois motivos: primeiro, matriz como argumento de função é automaticamente passada por referência por questão da implementação da linguagem, e segundo, é uma matriz de ponteiros e consequentemente sua declaração é caracterizada pelo asterisco na frente do nome da variável. Matrizes de ponteiros são usadas normalmente como ponteiros de strings como, por exemplo, o argumento da linha de comandos argv. 12.3.2 Acessando partes de Matrizes como vetores A linguagem C trata partes de matrizes como matrizes. Mais especificamente, cada linha de uma matriz de duas dimensões pode ser considerada como uma matriz de uma dimensão. Isto pode ser muito útil no tratamento de matrizes. O exemplo 13.8 mostra a atribuição de uma linha da matriz nome para um ponteiro. Exemplo 12.7 main() { static char nome[10][10]; char *p[10]; for (i = 0;i<10;i++) p[i] = nome[i]; ordena(p); } 12.4 Indireção Múltipla Indireção múltipla é uma situação onde o ponteiro aponta para um outro ponteiro e que o mesmo aponta para um valor final. A figura 12.1 mostra o conceito de indireção múltipla. Ponteiro Variável endereço valor Indireção Simples Ponteiro Ponteiro Variável endereço endereço valor LINGUAGEM C Ponteiros 60/103 Indireção Múltipla Figura 12.1 Indireção simples e múltipla A indireção múltipla pode ser levada a qualquer dimensão desejada, mas raramente é necessário mais de um ponteiro para um ponteiro. Obs.: Não confunda indireção múltipla com listas encadeadas. A declaração deste tipo de variável é feita colocando-se um * adicional em frente ao nome da variável, como mostra o exemplo 13.9. Tal exemplo mostra a declaração da variável ptrptrint como um ponteiro para um ponteiro do tipo int. Exemplo 12.1 int **ptrptrint; Para acessar o valor final apontado indiretamente por um ponteiro a um ponteiro, você deve utilizar o operador asterisco duas vezes, como no exemplo 13.10: Exemplo 12.2 #include “stdio.h” void main() { int x, *p, **q; x = 10; p = &x; q = &p; printf(“%d”, **q); /* imprime o valor de x */ } 12.5 Ponteiros para Funções A linguagem C permite apontadores para funções. Isto é permitido pois toda função tem uma posição física na memória que pode ser atribuída a um ponteiro. Portanto, um ponteiro de função pode ser usado para chamar uma função. O endereço de uma função é obtido usando o nome da função sem parênteses ou argumentos. Mas para declarar este tipo de apontador tem que se seguir uma sintaxe especial como mostra o exemplo 13.14. Exemplo 12.1 #include “stdio.h” #include “string.h” void check(char *a, char *b, int (*cmp)()); void main() { char s1[80], s2[80]; int (*p)(); p = strcmp; gets(s1); gets(s2); check(s1,s2,p); } void check(char *a, char *b, int (*cmp)()); { if (!(*cmp) (a, b)) printf(“igual”); else printf(“diferente”); } LINGUAGEM C Ponteiros 61/103 Quando a função check() é chamada, dois ponteiros para caracter e um ponteiro para uma função são passados como parâmetros. Dentro da função check(), note como o ponteiro para função é declarado, pois esta é a forma correta de se declarar este tipo de ponteiro. Os parênteses ao redor de *cmp são necessários para que o compilador interprete o comando corretamente. Uma outra forma de fazer a chamada é mostrada no exemplo 13.14 o qual dispensa o uso de um ponteiro adicional. check(s1, s2, strcmp); Uma das grandes utilidades é o uso de drivers de dispositivos (placas de som, placas de vídeo, modems, entre outros) que fornecem rotinas de tratamento para aquele hardware específico. Onde o programador lê o arquivo do driver para a memória e o executa de acordo com as especificações do fabricante. Outra utilidade é o programador poder enviar a função que se apropria para a comparação por exemplo. Isto é, no caso de strings pode-se pensar em um comparador de strings genérico onde como terceiro parâmetro é enviado a função que vai realizar a comparação. Antes da chamada da função genérica pode verificar se a string é composta por caracteres alfanuméricos (através da função isalpha()) e enviar a função strcmp(), caso contrário uma função que realize uma comparação de números inteiros (nesta função conterá a conversão das strings em um inteiro (função atoi()). 12.6 Mais Sobre declarações de Ponteiros As declarações de ponteiros podem ser complicadas e é necessário algum cuidado na sua interpretação. principalmente em declarações que envolvem funções e matrizes. Assim, a declaração int *p(int a); indica uma função que aceita um argumento inteiro e retorna um ponteiro para um inteiro. Por outro lado, a declaração int (*p)(int a); indica um ponteiro para uma função que aceita um argumento inteiro e retorna um inteiro. Nessa última declaração, o primeiro par de parênteses é usado para o aninhamento e o segundo par, para indicar uma função. A interpretação de declarações mais complexas pode ser extremamente mais trabalhosa. Por exemplo, considere a declaração int *(*p)(int (*a)[]); Nessa declaração, (*p)(..) indica um ponteiro para uma função. Por isso, int *(*p)(...) indica um ponteiro para uma função que retorna um ponteiro para um inteiro. Dentro do último par de parênteses (a especificação dos argumentos da função), (*a)[] indica um ponteiro para um vetor. Assim int (*a)[] representa um ponteiro para um vetor de inteiros. Juntando todas as peças, (*p)(int (*a)[]) representa um ponteiro para uma função cujo argumento é um ponteiro para um vetor de inteiros. E, finalmente, a declaração original representa um ponteiro para uma função que aceita um ponteiro para um vetor de inteiros como argumento e devolve um ponteiro para um inteiro. Lembre-se que se logo após um identificador existir um “abre parênteses” indica que o identificador representa uma função e os colchetes representam uma matriz. Os parênteses e colchetes têm maior precedência do que qualquer operador. A seguir será mostrado várias declarações envolvendo ponteiros e seu significado. LINGUAGEM C Ponteiros 62/103 int *p; p é um ponteiro para um valor inteiro int *p[10]; p é uma matriz de ponteiros com 10 elementos para valores inteiros int (*p)[10]; p é um ponteiro para uma matriz de inteiros com 10 elementos int *p(void); p é uma função que retorna um ponteiro para um valor inteiro int *p(char *a); p é uma função que aceita um argumento que é um ponteiro para um caracter e retorna um ponteiro para um valor inteiro int (*p)(char *a); p é m ponteiro para uma função que aceita um argumento que é um ponteiro para um caracter e retorna um valor inteiro int (*p(char *a))[10]; p é uma função que aceita um argumento que é um ponteiro para um caracter e retorna um ponteiro para uma matriz inteira de 10 elementos int p(char (*a)[]); p é uma função que aceita um argumento que é um ponteiro para uma matriz de caracter e retorna um valor inteiro int p(char *a[]); p é uma função que aceita um argumento que é uma matriz de ponteiros para caracter e retorna um valor inteiro int *p(char a[]); p é uma função que aceita um argumento que é uma matriz de caracter e retorna um ponteiro para um valor inteiro int *p(char (*a)[]); p é uma função que aceita um argumento que é um ponteiro para uma matriz de caracter e retorna um ponteiro para um valor inteiro int *p(char *a[]); p é uma função que aceita um argumento que é uma matriz de ponteiros para caracteres e retorna um ponteiro para um valor inteiro int (*p)(char (*a)[]); p é um ponteiro para uma função que aceita um argumento que é um ponteiro para uma matriz de caracter e retorna um valor inteiro int *(*p)(char (*a)[]); p é um ponteiro para uma função que aceita um argumento que é um ponteiro para uma matriz de caracter e retorna um ponteiro para um valor inteiro int *(*p)(char *a[]); p é um ponteiro para uma função que aceita um argumento que é uma matriz de ponteiros para caracteres e retorna um ponteiro para um valor inteiro int (*p[10])(char a); p é uma matriz de ponteiros com 10 elementos para funções; cada função aceita um argumento que é um LINGUAGEM C Ponteiros 63/103 caracter e retorna um valor inteiro int *(*p[10])(char a); p é uma matriz de ponteiros com 10 elementos para funções; cada função aceita um argumento que é um caracter e retorna um ponteiro para um valor inteiro int *(*p[10])(char *a); p é uma matriz de ponteiros com 10 elementos para funções; cada função aceita um argumento que é um ponteiro para um caracter e retorna um ponteiro para um valor inteiro 12.7 Exercícios 1. Como referenciar mat[x][y] em notação de ponteiros. 2. Qual será a saída deste programa? main() { int i=5; int *p; p = &i; printf(“%u %d %d %d %d \n”, p, *p+2,**&p,3**p,**&p+4); } 3. Escreva uma função que inverta a ordem dos caracteres de uma string. 4. Crie uma função que receba como parâmetro uma matriz de ponteiros para strings e devolve a matriz ordenada. 5. Faça uma função que receba um ponteiro para uma matriz e que realize a ordenação da mesma. 6. Faça a declaração de uma função (nome: teste) que receba um ponteiro para uma função que possui dois argumentos (int e char) e retorne um ponteiro para um float. 7. Faça a declaração e inicialização de uma matriz de ponteiros para os dias da semana. 8. Faça uma função que receba uma matriz de ponteiros para caracteres e realize a ordenação alfabética da mesma. LINGUAGEM C Estruturas e Uniões 64/103 13. Estruturas e Uniões A linguagem C permite criar tipos de dados definíveis pelo usuário de cinco formas diferentes. A primeira é estrutura, que é um agrupamento de variáveis sobre um nome e, algumas vezes, é chamada de tipo de dado conglomerado. O segundo tipo definido pelo usuário é o campo de bit, que é uma variação da estrutura que permite o fácil acesso aos bits dentro de uma palavra. O terceiro é a união, a qual permite que a mesma porção da memória seja definida por dois ou mais tipos diferentes de variáveis. Um quarto tipo de dado é a enumeração, que é uma lista de símbolos, como foi visto na seção 1.5. O último tipo definido pelo usuário é criado através do uso de typedef e define um novo nome para um tipo existente. 13.1 Estruturas O tipo estruturado struct possibilita a criação de estruturas de dados complexas, isto é, pode-se obter estruturas que contenham mais de um tipo de dado. Tal estrutura é conhecida em outras linguagens como registros. Cada elemento que compõe a estrutura (chamado campo) pode ser acessado individualmente, assim como a estrutura pode ser acessada como um todo. Em C, a declaração de uma estrutura é feita da seguinte forma: struct [nome_struct] { tipo var1; tipo var2; … tipo varN;} [nome_var]; Deve-se encerrar com um ponto-e-vírgula a declaração porque a definição de estrutura é na realidade uma instrução C. A declaração de estruturas pode se apresentar de diversas formas. Tais como: struct { char nome[30]; int idade; int codigo; float saldo; } conta1, conta2; Na declaração acima, o nome_struct não é utilizado pois esta estrutura será utilizada pelas variáveis de estrutura conta1 e conta2. Para utilizar esta estrutura na definição de outras variáveis tem-se que declará-las juntas com a definição da estrutura. No caso de um programa que utilize esta estrutura para passar parâmetros, declarar variáveis locais, entre outros, a linguagem permite a criação de rótulos de estruturas (nome_struct). struct cad_conta { char nome[30]; int idade; int codigo; float saldo; } conta1, conta2; Como mostra o exemplo acima, foram declaradas as variáveis conta1 e conta2 como sendo uma estrutura do tipo cad_conta. Quando rotula-se a estrutura pode-se omitir a declaração das variáveis, como é mostrado no exemplo abaixo: struct cad_conta { char nome[30]; int idade; LINGUAGEM C Estruturas e Uniões 65/103 int codigo; float saldo; }; Para usar esta estrutura em outras declarações deve-se especificar desta forma: struct cad_conta conta1, conta2; As estruturas seguem o padrão do escopo de variáveis, isto é, se a declaração estiver contida numa função, a estrutura tem escopo local para aquela função; se a declaração estiver fora de todas as funções, ela terá um escopo global. Para acessar um campo específico de uma struct utiliza-se o operador . (ponto). Exemplo 13.1 conta1.saldo = 0; conta1.codigo = 0; strcpy(conta1.nome,”Joao”); conta1.idade = 21; É permitida a atribuição entre struct. Neste caso todos os campos são copiados. Exemplo 13.2 conta2 = conta1; 13.1.1 Inicializando Estruturas Uma estrutura só pode ser inicializada se pertencer às classes static ou extern. Observe que a classe de uma estrutura é dada pelo ponto em que as variáveis foram declaradas e não pelo ponto onde a estrutura foi definida. Da mesma forma que os vetores, as estruturas são inicializadas com uma lista de valores (cada um correspondente a um campo de estrutura) entre chaves e separados por vírgulas. struct cad_conta { char nome[30]; int idade; int codigo; float saldo; }; main() { static struct cad_conta conta1 = {“Andre”, 23, 9507, 1567.89}, conta2 = {“Carlos”, 33, 9678, 1000.59}; … } 13.1.2 Estruturas Aninhadas Como os campos da estrutura podem ser de qualquer tipo, também é permitido o uso de estruturas na declaração. struct data { int dia; char mes[10]; int ano; }; struct func { char nome[20]; LINGUAGEM C Estruturas e Uniões 66/103 int codigo; float salario; struct data nascimento; }; main() { static struct func funcionario = {“Marcio”, 1234, 3743.44, {10, “Janeiro”, 1967}}, gerente = {“Jose”, 456, 5634.28, {18, “Marco”, 1950}}; } Observe a inicialização das variáveis. A estrutura é inicializada também com uma lista de valores entre chaves e separados por vírgulas. O acesso a um campo de uma estrutura aninhada é feito na forma: funcionário.nascimento.dia = 10; strcpy(gerente.nascimento.mes,”Abril”); 13.1.3 Estruturas e funções Em versões mais antigas de compiladores C, as estruturas não podiam ser usadas em passagem de parâmetros por valor para funções. Isto se devia a razões de eficiência, uma vez que uma estrutura pode ser muito grande e a cópia de todos os seus campos para a pilha poderia consumir um tempo exagerado. Desta forma, as estruturas eram obrigatoriamente passadas por referência, usando-se o operador de endereço (&). No Turbo C e outros compiladores mais recentes, a responsabilidade da decisão fica a cargo do programador. Assim, uma função pode passar ou retornar uma estrutura. struct cad_conta { char nome[30]; int idade; int codigo; float saldo; }; main() { static struct cad_conta conta1; conta1 = ins_conta(); lista(conta1); … } struct cad_conta ins_conta() { struct cad_conta aux; gets(aux.nome); scanf(“%d”, &aux.idade); scanf(“%d”, &aux.codigo); scanf(“%f”, &aux.saldo); return(aux); } void lista(aux) struct cad_conta aux; { printf(“Nome : %s\n”,aux.nome); printf(“Idade : %d\n”, aux.idade); printf(“Codigo : %d\n”, aux.codigo); printf(“Saldo : %.2f\n”, aux.saldo); } LINGUAGEM C Estruturas e Uniões 67/103 13.1.4 Vetor de Estruturas A criação de tabela de estruturas mantém a sintaxe normal de definição de matrizes, como é mostrada abaixo: struct cad_conta { char nome[30]; int idade; int codigo; float saldo; }; main() { int i static struct cad_conta conta[10]= { {“Andre”, 23, 9507, 1567.89}, {“Carlos”, 33, 9678, 1000.59}, ... }; for (i=0;i<10;i++) { printf(“Nome : %s\n”,conta[i].nome); printf(“Idade : %d\n”, conta[i].idade); printf(“Codigo : %d\n”, conta[i].codigo); printf(“Saldo : %.2f\n”, conta[i].saldo); } … } 13.1.5 Ponteiros para Estruturas C permite ponteiros para estruturas exatamente como permite ponteiros para outros tipos de variáveis. No entanto, há alguns aspectos especiais de ponteiros de estruturas. Como outros ponteiros, declara-se colocando um * na frente do nome da estrutura. No exemplo 12.3 declara-se ptr_cta como um apontador da estrutura previamente definida cad_conta. Exemplo 13.3 struct cad_conta *ptr_cta; Há dois usos primários para ponteiros de estrutura: gerar uma chamada por referência para uma função e criar estruturas de dados dinâmicas (listas, pilhas, filas, entre outras) utilizando-se do sistema de alocação de C. Na forma de acessar os elementos ou campos de uma estrutura usando um ponteiro para a estrutura, deve-se utilizar o operador -> (seta). A seta é usada sempre no caso de apontador de estruturas. No exemplo abaixo é mostrada a declaração, atribuição e utilização de ponteiros de estruturas. Exemplo 13.4 struct cad_conta { char nome[30]; int idade; int codigo; float saldo; } conta; main() { struct cad_conta *ptr; LINGUAGEM C Estruturas e Uniões 68/103 ptr = &conta; /* o ponteiro recebe o endereco da estrutura a ser apontada */ ptr->idade = 23; ptr->codigo = 1000; } 13.2 Campos de Bits Ao contrário das linguagens de computador, C tem um método intrínseco para acessar um único bit dentro de um byte. Isso pode ser útil por um certo número de razões: • Se o armazenamento é limitado, você pode armazenar diversas variáveis Booleanas (verdadeiro/falso) em um byte. • Certos dispositivos transmitem informações codificadas nos bits dentro de um byte. • Certas rotinas de criptografia precisam acessar os bits dentro de um byte. Para acessar os bits, C usa um método baseado na estrutura. Um campo de bits é, na verdade, apenas um tipo de elemento de estrutura que define o comprimento, em bits, do campo. A forma geral de uma definição de campo de bit é: struct nome { tipo var1 : comprimento; tipo var2 : comprimento; … tipo varN : comprimento;} [lista_de_variaveis]; Um campo de bit deve ser declarado como int, unsigned ou signed. Campos de bit de comprimento 1 devem ser declarados como unsigned, porque um único bit não pode ter sinal. (Alguns compiladores só permitem campos do tipo unsigned). Um exemplo de campos de bits é a comunicação via serial que devolve um byte de estado organizado desta forma: Bit Significado quando ligado 0 alteração na linha clear-to-send 1 alteração em data-set-ready 2 borda de subida da portadora detectada 3 alteração na linha de recepção 4 clear-to-send 5 data-set-ready 6 chamada do telefone 7 sinal recebido Pode-se representar a informação em um byte de estado utilizando o seguinte campo de bits: struct status_type { unsigned delta_cts : 1; unsigned delta_dsr : 1; unsigned tr_edge : 1; unsigned delta_rec : 1; unsigned cts : 1; unsigned dsr : 1; unsigned ring : 1; unsigned rec_line : 1; } status; LINGUAGEM C Estruturas e Uniões 69/103 Para atribuir um valor a um campo de bit, simplesmente utiliza-se a forma para atribuição de outro tipo de elemento de estrutura. status.ring = 0; Não é necessário dar um nome a todo campo de bit. Isto torna fácil alcançar o bit que se deseja acessar, contornando os não usados. Por exemplo, se apenas cts e dtr importam, pode-se declarar a estrutura status_type desta forma: struct status_type { unsigned : 4; unsigned cts : 1; unsigned dsr : 1; } status; Além disso, nota-se que os bits após dsr não precisam ser especificados se não são usados. Variáveis de campo de bit têm certas restrições: • Não pode obter o endereço de uma variável de campo de bit. • Variáveis de campo de bit não podem ser organizadas em matrizes. • Não pode ultrapassar os limites de um inteiro. • Não pode saber, de máquina para máquina, se os campos estarão dispostos da esquerda para a direita ou da direita para a esquerda. • Em outras palavras, qualquer código que use campos de bits pode ter algumas dependências da máquina. Finalmente, é válido misturar elementos normais de estrutura com elementos de campos de bit. Por exemplo, struct emp { struct addr address; float pay; unsigned lau_off : 1; /* ocioso ou ativo */ unsigned hourly : 1; /* pagamento por horas */ unsigned deduction : 3; /* deduções de imposto */ }; define um registro de um empregado que usa apenas um byte para conter três informações: o estado do empregado, se o empregado é assalariado e o número de deduções. Sem o campo de bits, essa variável ocuparia três bytes. 13.3 Uniões Uma união é um tipo de dado que pode ser usado de muitas maneiras diferentes. Por exemplo, uma união pode ser interpretada como sendo um inteiro numa operação e um float ou double em outra. Embora, as uniões possam tomar a aparência de uma estrutura, elas são muito diferentes. Uma união pode conter um grupo de muitos tipos de dados, todos eles compartilhando a mesma localização na memória. No entanto, uma união só pode conter informações de um tipo de dados de cada vez. Para criar uma união utiliza-se a seguinte sintaxe: union [nome_union] { tipo var1; tipo var2; … tipo varN;} [nome_var]; LINGUAGEM C Estruturas e Uniões 70/103 Deve-se encerrar com um ponto-e-vírgula a declaração porque a definição de união é na realidade uma instrução C. A declaração de uniões pode se apresentar de diversas formas. Tais como: union { char c; int i; double d; float f; } data; Na declaração acima, o nome_union não é utilizado pois esta união será utilizada pela variável data. Para utilizar esta união na definição de outras variáveis tem-se que declará-las juntas com a definição da união. No caso de um programa que utilize esta união em várias partes do programa a linguagem C permite a criação de rótulos de estruturas (nome_union). union tipos { char c; int i; double d; float f; } data; Como mostra o exemplo acima, foi declarada a variável data como sendo uma união do tipo tipos. Quando rotula-se a união pode-se omitir a declaração das variáveis, como é mostrado no exemplo abaixo: union tipos { char c; int i; double d; float f; }; Para usar esta união em outras declarações deve-se especificar desta forma: union tipos data1, data2; As estruturas seguem o padrão do escopo de variáveis, isto é, se a declaração estiver contida numa função, a estrutura tem escopo local para aquela função; se a declaração estiver fora de todas as funções, ela terá um escopo global. Para acessar um campo específico de uma union utiliza-se o operador . (ponto). Pode-se declarar estruturas dentro de uniões. struct so_int { int i1,i2; }; struct so_float { float f1,f2; }; union { struct so_int i; struct so_float f; } teste; main() { teste.i.i1 = 2; teste.i.i2 = 3; printf(“i1 = %-3d i2 = %-3d\n”,teste.i.i1,teste.i.i2); teste.f.f1 = 2.5; teste.f.f2 = 3.5; printf(“f1 = %.1f f2 = %.1f\n”,teste.f.f1,teste.f.f2); LINGUAGEM C Estruturas e Uniões 71/103 13.4 Sizeof() Com uso de estruturas, uniões e enumerações pode-se utilizá-las para a criação de variáveis de diferentes tamanhos e que o tamanho real dessas variáveis pode mudar de máquina para máquina. O operador unário sizeof() calcula o tamanho de qualquer variável ou tipo e pode ajudar a eliminar códigos dependentes da máquina de seus programas. Exemplo 13.5 union tipos { char c; int i; double d; float f; } data; O sizeof(data) é 8. No tempo de execução, não importa o que a união data está realmente guardando. Tudo o que importa é o tamanho da maior variável que pode ser armazenada porque a união tem de ser do tamanho do seu maior elemento. 13.5 Typedef C permite que defina-se explicitamente novos nomes aos tipos de dados, utilizando a palavra- chave typedef. Não há criação de uma nova variável, mas sim, definindo-se um novo nome para um tipo já existente. Serve para uma boa documentação ou até tornar os programas dependentes de máquina um pouco mais portáteis. A forma geral de um comando typedef é typedef tipo nome; Por exemplo, poderia ser criado um novo nome para char utilizando typedef char boolean; Esse comando diz ao compilador para reconhecer boolean como outro nome para char. Assim, para se criar uma variável char, usando boolean boolean ok; Também é válida a redefinição, isto é, utilizar um novo nome para um nome atribuído a um dado previamente estabelecido. #include <stdio.h> typedef char boolean; typedef boolean bool; void main() { boolean a; bool b; a = 1; b = 2; printf("%d %d",a,b); } A declaração typedef é usado também para definir tipos estruturados (struct e union) para facilitar a nomenclatura dos tipos na declaração de variáveis (exemplo 12.6) LINGUAGEM C Estruturas e Uniões 72/103 Exemplo 13.6 typedef struct conta { char nome[30]; int idade; int codigo; float saldo; } cad_conta; main() { cad_conta *ptr; ptr = &conta; ptr->idade = 23; ptr->codigo = 1000; } ou struct conta { char nome[30]; int idade; int codigo; float saldo; }; typedef struct conta cad_conta; main() { cad_conta *ptr; ptr = &conta; ptr->idade = 23; ptr->codigo = 1000; } 13.6 Exercícios 1. Faça um programa que leia os dados de 10 clientes de um banco e após leia 100 conjuntos de 3 valores: • código de operação - 0 depósito, 1 - retirada, • valor da operação • código do cliente. Realize as movimentações nas contas correspondentes e ao final escreva o nome e saldo de cada cliente. 2. Faça um programa de cadastro de clientes que contenham as seguintes opções: incluir, alteração, excluir e consultar por código ou por nome. O cadastro deve ser da seguinte forma: • nome (30 caracteres); • código (0 a 255); • idade(char); LINGUAGEM C Alocação Dinâmica 73/103 14. Alocação Dinâmica Programas consistem em duas coisas: algoritmos e estruturas de dados. Um bom programa é uma combinação de ambos. A escolha e a implementação de uma estrutura de dados são tão importantes quanto as rotinas que manipulam os dados. Para a manipulação de dados é utilizado mecanismos que auxiliam tanto na forma de como é armazenado ou recuperado. Existem vários mecanismos que realizam este tipo de processamento. Abaixo estão listados alguns mecanismos básicos: • Listas • Pilhas • Filas • Árvores Cada um destes mecanismos pode ter variações de acordo com a política de processamento (armazenamento/recuperação). Neste capítulo será abordado com mais ênfase as listas encadeadas, porque serão como base para a construção dos demais. 14.1 Funções de Alocação dinâmica em C A memória alocada pelas funções de alocação dinâmica de C é obtida do heap - a região de memória livre que está o programa, a área permanente e a pilha. As funções são utilizadas para alocar e desalocar esta memória, o que estiver livre. O padrão C ANSI define apenas quatro funções para o sistema de alocação dinâmica: calloc(), malloc(), free(), realloc(). No entanto, serão estudadas, além das funções descritas, algumas funções que estão sendo largamente utilizadas. O padrão C ANSI especifica que os protótipos para as funções de alocação dinâmica definidas pelo padrão estão em STDLIB.H. Entretanto, tais funções estão especificadas na biblioteca ALLOC.H, onde encontram-se mais funções de alocação dinâmica. O padrão C ANSI especifica que o sistema de alocação dinâmica devolve ponteiros void, que são ponteiros genéricos, podendo apontar para qualquer objeto. Porém, alguns compiladores mais antigos devolvem ponteiros para char. Nesse caso, deve-se usar um cast quando atribuir a ponteiros de tipos diferentes. 14.1.1 Função malloc() Esta função devolve um ponteiro para o primeiro byte de uma região de memória de tamanho size que foi alocada do heap. No caso em que não houver memória suficiente, a função devolve um ponteiro nulo. Cuidado! Ao usar um ponteiro nulo, pode ocorrer uma quebra do sistema. Sintaxe.: void *malloc(size_t size); Onde size_t pode ser considerado um inteiro sem sinal e size é o número de bytes de memória que se quer alocar. Essa função devolve um ponteiro void, como mostra a sintaxe, portanto pode-se atribuir a qualquer tipo de ponteiro. LINGUAGEM C Alocação Dinâmica 74/103 Para assegurar a portabilidade de um programa que utilize a alocação dinâmica, faz-se necessário a utilização da função sizeof(). Exemplo 14.1 Esta função aloca memória suficiente para conter uma estrutura do tipo addr: struct addr { char nome[40]; char rua[40]; char cidade[40]; char estado[3]; char cep[10];}; struct addr *get_struct(void) { struct addr *p; if ((p=(struct addr*)malloc(sizeof(addr)))==NULL) { printf(“ erro de alocação - abortando”); exit(1); } return p; } O fragmento do código mostra a alocação de 1000 bytes de memória. char *p; p = (char*)malloc(1000); No fragmento abaixo é alocado memória suficiente para 50 inteiros. int *p; p = (int*)malloc(50 * sizeof(int)); O compilador deve conhecer duas informações sobre qualquer ponteiro: o endereço da variável apontada e seu tipo. Por isso, precisa-se fazer uma conversão de tipo (cast) do valor retornado por malloc(), já que o mesmo retorna um void. Portanto, no exemplo 13.15 deve-se indicar ao compilador que o valor retornado por malloc() é do tipo ponteiro para struct addr. p=(struct addr*) malloc(sizeof(addr)) Este tipo de conversão deve ser realizado em todas as funções de alocação como calloc(), realloc() e malloc(). 14.1.2 Função calloc() Esta função devolve um ponteiro para o primeiro byte de uma região de memória de tamanho size * num que foi alocada do heap. No caso em que não houver memória suficiente, a função devolve um ponteiro nulo. Sintaxe.: void *calloc(size_t num, size_t size); Onde size_t pode ser considerado um inteiro sem sinal e size é o número de bytes de memória que se quer alocar. Essa função devolve um ponteiro void, como mostra a sintaxe, portanto pode-se atribuir a qualquer tipo de ponteiro. LINGUAGEM C Alocação Dinâmica 75/103 Para assegurar a portabilidade de um programa que utilize a alocação dinâmica, faz-se necessário a utilização da função sizeof(). A diferença entre calloc() e malloc() é que a primeira aloca a memória e inicializa-a com zeros. Exemplo 14.2 Esta função aloca memória suficiente para conter umvetor de 100 elementos: #include “stdlib.h” #include “stdio.h” float *get_mem(void) { float *p; p=(float*)calloc(100, sizeof(float)); if (!p) { printf(“ erro de alocação - abortando”); exit(1); } return p; } No fragmento abaixo é alocado memória suficiente para 50 inteiros. int *p; p = (int*)calloc(50,sizeof(int)); 14.1.3 Função free() Esta função devolve ao heap a memória apontada por ptr, tornando a memória disponível para alocação futura. free() deve ser chamada somente com um ponteiro que foi previamente alocado com uma das funções do sistema de alocação dinâmica. A utilização de um ponteiro inválido na chamada provavelmente destruirá o mecanismo de gerenciamento de memória e provocará uma quebra do sistema. Exemplo 14.3 #include <string.h> #include <stdio.h> #include <alloc.h> int main(void) { char *str; /* aloca memoria para uma string */ str = (char*)malloc(10); /* copia "Hello" para a string */ strcpy(str, "Hello"); /* mostra a string */ printf("String: %s\n", str); /* libera a memoria */ free(str); return 0; } LINGUAGEM C Alocação Dinâmica 76/103 14.1.4 Função realloc() Esta função modifica o tamanho da memória previamente alocada apontada por ptr para aquele especificado por size. O valor de size pode ser maior ou menor que o original. Um ponteiro para o bloco de memória é devolvido porque realloc() pode precisar mover o bloco para aumentar seu tamanho. Se isso ocorre, o conteúdo do bloco antigo é copiado no novo bloco; nenhuma informação é perdida. Sintaxe.: void *realloc(void *ptr, size_t size); Se ptr é um nulo, realloc() simplesmente aloca size bytes de memória e devolve um ponteiro para a memória alocada. Se size é zero, a memória apontada por ptr é liberada. Se não há memória livre suficiente no heap para alocar size bytes, é devolvido um ponteiro nulo e o bloco original é deixado inalterado. Exemplo 14.4 Esta programa primeiro aloca 23 caracteres, copia a string “isso são 22 caracteres” neles e, em seguida, usa realloc() para aumentar o tamanho para 24 e, assim, pôr um ponto no final. #include “stdlib.h” #include “stdio.h” #include “string.h” void main(void) { char *p; p=(char*)malloc(23); if (!p) { printf(“ erro de alocação - abortando”); exit(1); } strcpy(p,”isso são 22 caracteres”); p = (char*)realloc(p,24); if (!p) { printf(“ erro de alocação - abortando”); exit(1); } strcat(p,”.”); printf(p); free(p); } 14.2 Matrizes Dinamicamente Alocadas Qualquer ponteiro pode ser indexado como se fosse uma matriz unidimensional, portanto não haverá nenhum problema para utilizar (exemplo 13.19). Exemplo 14.6 /* Aloca memória para uma string dinamicamente, solicita */ /* a entrada do usuário e, em seguida, imprime a string */ /* de trás para frente. */ #include “stdlib.h” #include “stdio.h” LINGUAGEM C Alocação Dinâmica 77/103 #include “string.h” void main(void) { char *s; register int t; s=(char*)malloc(80); if (!s) { printf(“ erro de alocação - abortando”); exit(1); } gets(s); for (t=strlen(s)-1; t>=0; t--) putchar(s[t]); free(s); } Para acessar uma matriz unidimensional é simples, mas para mais de uma dimensão levam alguns problemas com a indexação. Para conseguir uma matriz alocada dinamicamente, deve-se utilizar um truque: passar um ponteiro como um parâmetro a uma função. Dessa forma, a função pode definir as dimensões do parâmetro que recebe o ponteiro, permitindo, assim, a indexação normal da matriz. Isto é mostrado no exemplo 13.20. Exemplo 14.7 #include “stdlib.h” #include “stdio.h” #include “string.h” void le_tab(int mat[20][5]) { register int i,j; for(i=0; i<20; i++) for(j=0; j<5; j++) scanf(“%d”,&mat[i][j]); } void mostra_tab(int mat[20][5]) { register int i,j; for(i=0; i<20; i++) for(j=0; j<5; j++) printf(“%d”,mat[i][j]); } void main(void) { char *p; register int t; s=(int*)calloc(100, sizeof(int)); if (!p) { printf(“ erro de alocação - abortando”); exit(1); } le_tab(p); mostra_tab(p); } LINGUAGEM C Alocação Dinâmica 78/103 14.3 Listas Encadeadas Listas encadeadas são usadas para dois propósitos fundamentais. O primeiro é criar matrizes de tamanho desconhecido na memória. Listas encadeadas também são usadas em armazenamento de banco de dados em arquivos em disco. Listas encadeadas podem ser singularmente (simplesmente) - um elo para o próximo item - ou duplamente - elos para o anterior e próximo elemento da lista - encadeadas. 14.3.1 Listas Singularmente Encadeadas Uma lista singularmente encadeada requer que cada item de informação contenha um elo como o próximo da lista. Cada item de dado geralmente consiste em uma estrutura que inclui campos de informação e ponteiro de enlace (ou de ligação). Antes, precisa-se definir a estrutura de dados que contenha a informação e os elos. Considere um exemplo de armazenamento de coordenadas cartesianas (x,y,z) para representação de uma figura geométrica. A estrutura de dados para cada elemento é definido aqui: struct ponto { int x,y,z; struct ponto *prox; } figura1; typedef struct ponto figura; Na estrutura acima, é declarado um apontador para a próxima estrutura, por isso declara-se um apontador para a própria estrutura (auto-referência). Após a declaração, foi definido que figura é um tipo, que representa uma struct ponto, o qual pode ser utilizado em todo o programa. A função inclui() constrói uma lista singularmente encadeada colocando cada novo item no final da lista. Deve ser passado um ponteiro para uma estrutura do tipo ponto, ponteiro para o primeiro elemento e ponteiro para o último elemento. void inclui(figura *i, figura **inicio, figura **fim) { if (!*fim) /* Primeiro elemento da lista */ { *fim = i; *inicio = i; } else { (*fim)->prox = i; *fim = i; } } Os parâmetros início e fim da função têm dois asteriscos porque representam uma indireção múltipla. Isto é, são apontadores para apontadores da estrutura figura. Isto é necessário para poder implementar a passagem de parâmetros por referência. Apagar um item de uma lista singularmente encadeada pode ocorrer em três situações: apagar o primeiro item, apagar um item intermediário e apagar o último item. A função a seguir excluirá um item de uma lista de estruturas do tipo ponto: void exclui( figura *p, /* item anterior */ figura *i, /* item a apagar */ LINGUAGEM C Alocação Dinâmica 79/103 figura **inicio, /* início da lista */ figura **ultimo) /* final da lista */ { if (p) p->next = i->next; else *start = i->next; if (i==*last && p) *last = p; } Listas singularmente encadeadas têm uma desvantagem é que a lista não pode ser lida em ordem inversa. 14.3.2 Listas Duplamente Encadeadas Consistem em dados e elos para o próximo item e para o item precedente. Um novo elemento pode ser inserido em uma lista duplamente encadeada de três maneiras: inserir um novo primeiro elemento, inserir um elemento intermediário ou inserir um novo último elemento. A construção de uma lista duplamente encadeada é semelhante à de uma lista singularmente encadeada, exceto pelo fato de que dois elos devem ser mantidos. Utilizando a estrutura ponto, será mostrado a declaração de um nodo de lista duplamente encadeada. struct ponto { int x,y,; struct ponto *prox; struct ponto *ant; }; typedef struct ponto figura; Usando figura como o item de dado básico, a função seguinte constrói uma lista duplamente encadeada. Esta função inclui um novo dado no fim da lista: void incfim(figura *i, figura **fim) { if (!*fim) *fim = i; /* é o primeiro item da lista */ else (*fim)->prox = i; i->prox = NULL; i->ant = *fim; *fim = i; } Para armazenagem de um dados em uma posição específica a função abaixo realiza a inclusão em ordem crescente pelo eixo x de uma lista duplamente encadeada. void incord(figura *i, /* novo elemento */ figura **inicio, /* primeiro elemento da lista */ figura **fim) /* ultimo elemento da lista */ { if (!*fim) /* é o primeiro item da lista */ { i->prox = NULL; i->ant = NULL; *inicio = i; *fim = i; } else { LINGUAGEM C Alocação Dinâmica 80/103 figura *old, *p; p = *inicio; old = NULL; while (p && (p->x < i->x)) { old = p; p = p->prox; } if (!old) { i->prox = p; /* inserir no inicio da lista */ i->ant = NULL; p->ant = i; *inicio = i; } else { if (p->ant) { /* inserir em uma posição */ p->ant->prox = i; /* intermediária da lista */ i->prox = p; i->ant = p->ant; p->ant = i; } else { old->prox = i; /* inserir no fim da lista */ i->prox = NULL; i->ant = old; *fim = i; } } } Como o ponteiro de início e fim de lista podem ser alterados, a função incord() atualiza automaticamente estes ponteiros através das variáveis inicio e fim. Há três casos a considerar ao excluir um elemento de uma lista duplamente encadeada: excluir o primeiro item, excluir um item intermediário ou excluir o último item. void delord(figura *i, /* item a apagar */ figura **inicio, /* primeiro elemento da lista */ figura **fim) /* ultimo elemento da lista */ { figura *old, *p; p = *inicio; old = NULL; while (p && (p->x != i->x) &&(p->y != i->y)) { old = p; p = p->prox; } if ((p->x = i->x) &&(p->y = i->y)) { if (!old) /* exclusao unico elemento da lista */ *inicio=*fim=NULL; else { if (!p->prox) * exclusao do ultimo elemento da lista */ { old->prox = NULL; *fim = old; } else { /* excluir item de uma posição */ old->prox = p->prox; /* intermediária da lista */ p->prox->ant = old; LINGUAGEM C Alocação Dinâmica 81/103 } } free(p); } } 14.4 Árvores Binárias A estrutura utilizada para a cosntrução de árvores binárias é semelhante a listas duplamente encadeadas. A diferença está na política de organização das mesmas. A função abaixo constrói uma árvore binária ordenada recursivamente: struct tree { char info; struct tree *esq; struct tree *dir; }; typedef struct tree arvore; arvore *stree ( arvore *raiz; arvore *r; char info); { if (!r) { r = (arvore *) malloc(sizeof(arvore)); if (!r) { printf(“Sem memória \n”); exit(0); } r->esq = NULL; r->dir = NULL; r->info = info; if (!raiz) return r; /* primeira entrada */ if (info < raiz->info) raiz->esq = r; else raiz->dir = r; return r; } if (info < r->info) stree(r,r->esq, info); else stree(r,r->dir, info); A chamada desta função é realizada desta forma: if (!rt) rt = stree(rt, rt, info); else stree (rt, rt, info); Dessa forma, tanto o primeiro quanto os elementos subsequentes podem ser inseridos corretamente. A função stree() é um algoritmo recursivo. Existem três formas de acessar os dados de uma árvore: ordenada, preordenada e pós-ordenada. Onde a ordenada, é visitado a subárvore da esquerda, a raiz e em seguida a subárvore da direita. Na LINGUAGEM C Alocação Dinâmica 82/103 preordenada, visita-se a raiz, subárvore da esquerda e, em seguida, a subárvore da direita. Na pós- ordenada, visita-se a subárvore da esquerda, subárvore da direita e, depois, a raiz. d b f a c e g Figura 14.1 Exemplo de uma árvore binária Utilizando a figura 14.1, a ordem de acesso á árvore usando cada método é ordenada a b c d e f g preordenada d b a c f e g pós-ordenada a c b e g f d Para o acesso de forma ordenada, pelas formas descritas anteriormente, pode-se utilizar as funções descritas abaixo: void inorder(arvore *raiz) { if(!raiz) return; inorder(raiz->esq); printf(“%c”, raiz->info); inorder(raiz->dir); } void preorder(arvore *raiz) { if(!raiz) return; printf(“%c”, raiz->info); preorder(raiz->esq); preorder(raiz->dir); } void postorder(arvore *raiz) { if(!raiz) return; postorder(raiz->esq); postorder(raiz->dir); printf(“%c”, raiz->info); } Para exclusão de um nó de uma árvore tem que ser verificado se o nó é a raiz, um nodo esquerdo ou direito e que os mesmos podem ter subárvores ligadas a ele. Na função a seguir é realizada uma exclusão recursiva observando as restrições delineadas anteriormente. arvore *dtree(arvore *raiz, char key) { arvore *p, *p2; if (raiz->info==key) /* apagar a raiz */ { if (raiz->esq== raiz->dir) /*não tem filhos */ { LINGUAGEM C Alocação Dinâmica 83/103 free(raiz); return NULL; } else if (raiz->esq == NULL) { p = raiz->dir; free(raiz); return p; } else if (raiz->dir == NULL) { p = raiz->esq; free(raiz); return p; } else { p2 = raiz->dir; p = raiz->dir; while (p->esq) p = p->esq; p->esq = raiz->esq; free(raiz); return p2; } } if (raiz->info < key) raiz->dir = dtree(raiz->dir, key); else raiz->esq = dtree(raiz->esq, key); return raiz; } Árvores binárias oferecem grande poder, flexibilidade e eficiência quando usadas em programas de gerenciamento de banco de dados. Principalmente pela sua política de organização e a não limitação do tamanho (exceto aquela imposta pela memória). 14.5 Exercícios 1. Escreva um programa que leia vários nomes e endereços, rearranje os nomes em ordem alfabética e, depois, imprima a lista em ordem alfabética. Utilize várias estruturas. 2. Escreva um programa que gerencie uma pilha. O mesmo deve conter a função de empilhar e desempilhar para o usuário os quatro tipos de dados básicos da linguagem C (char, float, int, double). 3. Escreva um programa que gerencie uma fila circular do tipo FIFO (Primeiro que entra é o primeiro que sai). 9. Faça um programa que leia o número de alunos; construa uma matriz dinamicamente alocada de tamanho N X 4, onde N é o número de alunos e 4 as respectivas notas de cada aluno. Calcule a média e mostre na tela conforme descrição a seguir: ALUNO N1 N2 N3 N4 MEDIA 1 8.5 7.0 9.5 7.0 8.0 2 7.5 7.0 6.5 7.0 7.0 LINGUAGEM C E/S com Arquivo 84/103 15. E/S com Arquivo São grupos de dados armazenados em meio não volátil (disco, fita, entre outros). São utilizados para armazenar dados de forma permanente. A linguagem C não contém nenhum comando de E/S. Ao contrário, todas as operações de E/S ocorrem através de chamadas a funções da biblioteca C padrão. Essa abordagem faz o sistema de arquivos de C extremamente poderoso e flexível. O sistema de E/S de C é único, porque dados podem ser transferidos na sua representação binária interna ou em um formato de texto. 15.1 E/S ANSI x E/S UNIX O padrão ANSI define um conjunto completo de funções de E/S que pode ser utilizado para ler e escrever qualquer tipo de dado. Em contraste, o antigo padrão C UNIX contém dois sistemas distintos de rotinas que realizam operações de E/S. O primeiro método é denominado de sistema de arquivo com buffer (algumas vezes os termos formatado ou alto nível são utilizados para referenciá-lo). O segundo é o sistema de arquivo tipo UNIX (algumas vezes chamado de não formatado ou sem buffer) definido apenas sob o antigo padrão UNIX. 15.2 Streams A linguagem C oferece uma abstração da interface para controle de E/S, independente do dispositivo real (terminais, acionadores de disco, acionadores de fita, entre outros) que é acessado. Sendo que o dispositivo real é chamado de arquivo. Existem dois tipos de streams: texto e binária. A primeira é uma seqüência de caracteres organizados em linhas e terminadas por um caractere de nova linha (depende da implementação). A segunda é uma seqüência de bytes com uma correspondência de um para um com aqueles encontrados no dispositivo externo - isto é, não ocorre nenhuma tradução de caracteres. 15.3 Arquivos Um arquivo pode ser qualquer coisa, desde um arquivo em disco até um terminal ou uma impressora. Associa-se uma stream com um arquivo específico realizando uma operação de abertura. Todos as streams são iguais, mas não todos os arquivos. Isto é, um arquivo disco pode suportar acesso aleatório enquanto um teclado não pode. Cada stream associada a um arquivo tem uma estrutura de controle de arquivo do tipo FILE. Essa estrutura é definida no cabeçalho STDIO.H. Todos os arquivos são fechados automaticamente quando o programa termina, normalmente com main() retornando ao sistema operacional ou uma chamada à exit(). Os arquivos não são fechados quando um programa quebra (crash). LINGUAGEM C E/S com Arquivo 85/103 15.4 Sistema de Arquivos O sistema de arquivos ANSI é composto de diversas funções inter-relacionadas. As mais comuns são mostradas na tabela 15.1. Essas funções exigem que o cabeçalho STDIO.H seja incluído em qualquer programa em que são utilizadas. Tabela 15.1 - Funções mais comuns do sistema de arquivo com buffer Nome Função fopen() Abre um arquivo fclose() Fecha um arquivo putc() Escreve um caractere em um arquivo fputc() O mesmo que putc() getc() Lê um caractere de um arquivo fgetc() O mesmo que getc() fseek() Posiciona o arquivo em um byte específico fprintf() É para um arquivo o que printf() é para o console fscanf() É para um arquivo o que scanf() é para o console feof() Devolve verdadeiro se o fim de arquivo for atingido ferror() Devolve verdadeiro se ocorreu um erro rewind() Repõe o indicador de posição de arquivo no início do arquivo remove() Apaga um arquivo fflush() Descarrega um arquivo O arquivo cabeçalho STDIO.H fornece os protótipos para as funções de E/S e define estes três tipos: size_t, fpos_t e FILE. O tipo size_t é essencialmente o mesmo que um unsigned, assim como o fpos_t. O tipo FILE é discutido na próxima seção. STDIO.H define várias macros como: EOF, SEEK_SET, SEEK_CUR e SEEK_END. A macro EOF é geralmente definida como -1 e é o valor quando uma função de entrada tenta ler além do final do arquivo. As outras macros são usadas com fseek(), que é uma função que executa acesso aleatório em um arquivo. 15.5 Estrutura FILE Para a manipulação de arquivos é utilizado a declaração de ponteiro (ponteiro de arquivo). Isto é, um ponteiro para informações que definem vários dados sobre o arquivo, como o seu nome, status, e a posição atual do arquivo. Um ponteiro de arquivo é uma variável ponteiro do tipo FILE . Todas as funções são realizadas utilizando o ponteiro. Para a declaração de um ponteiro de arquivo utiliza-se a seguinte sintaxe: Sintaxe: FILE *<var> Exemplo 15.1 FILE *fp; 15.6 Abertura de Arquivos A função fopen() abre uma stream para uso e associa um arquivo a ela. Retorna o ponteiro de arquivo associado a esse arquivo. Sintaxe: LINGUAGEM C E/S com Arquivo 86/103 FILE *fopen(const char * <nome_ arquivo>, const char *<modo_abertura>); O modo de abertura define a forma como é feito o acesso aos dados (somente leitura, leitura e escrita, etc). As forma principais são apresentadas na tabela 15.2. Tabela 15.2 - Os modos de abertura válidos Modo Significado r Abre um arquivo texto para leitura w Cria um arquivo texto para escrita a Anexa a um arquivo texto rb Abre um arquivo binário para leitura wb Cria um arquivo binário para escrita ab Anexa a um arquivo binário r+ Abre um arquivo texto para leitura/escrita w+ Cria um arquivo texto para leitura/escrita a+ Anexa ou cria um arquivo texto para leitura/escrita r+b ou rb+ Abre um arquivo binário para leitura/escrita w+b ou wb+ Cria um arquivo binário para leitura/escrita a+b ou ab+ Anexa a um arquivo binário para leitura/escrita Exemplo 15.2 FILE *arq; /* ponteiro de arquivo */ arq = fopen(“dados.dat”,”wb”); Se ao abrir um arquivo para leitura o mesmo não existir a função fopen retorna um ponteiro nulo (NULL). arq = fopen(“dados.dat”,”rb”); if (arq= =NULL) arq=fopen(“dados.dat”,”wb”); 15.7 Fechamento de Arquivo A função fclose() fecha uma stream que foi aberta através de uma chamada à fopen(). Esta função tem a seguinte sintaxe: Sintaxe: int fclose(FILE *fp); onde fp é o ponteiro de arquivo devolvido pela chamada à fopen(). O valor de retorno 0 significa uma operação de fechamento bem-sucedida. Qualquer outro valor indica erro, que pode ser diagnosticada pela função ferror() (discutida mais adiante). 15.8 Leitura e Gravação de caracteres Para as operações de leitura e gravação são utilizadas duas funções padrões: getc() e putc() (consideradas tecnicamente macros) . Para cada uma destas funções existem duas equivalentes: fgetc() e fputc(). Nas seções a seguir serão estudadas as funções declaradas padrão ANSI (as duas primeiras). As outras funções têm a mesma sintaxe que suas equivalentes. LINGUAGEM C E/S com Arquivo 87/103 15.8.1 Gravação Para escrita de caracteres em um arquivo utilizam-se as funções putc() e fputc(), as quais são equivalentes (putc() é uma macro). O protótipo para essa função é Sintaxe: int putc(int ch, FILE *fp); onde fp é um ponteiro de arquivo devolvido por fopen() e ch é o caractere a ser escrito. Se a operação putc() foi bem-sucedida, ela devolve o caractere escrito. Caso contrário, ela devolve EOF. 15.8.2 Leitura Para leitura de caracteres em um arquivo utilizam-se as funções getc() e fgetc(), as quais são equivalentes (getc() é uma macro). O protótipo para essa função é Sintaxe: int getc(FILE *fp); onde fp é um ponteiro de arquivo devolvido por fopen(). Se a operação getc() foi bem-sucedida, ela devolve o caractere lido. Caso contrário, ela devolve EOF. O exemplo 15.3 mostra um laço que realiza uma leitura de um arquivo texto até que a marca de final de arquivo seja lida. Exemplo 15.3 do { ch = getc(fp); } while (ch!=EOF); 15.9 Verificando fim de arquivo Além de realizar um teste para fim de arquivo como no exemplo 15.3 pode-se utilizar uma função como feof() que devolve verdadeiro quando for encontrado o fim de arquivo. O protótipo desta função está declarado abaixo: Sintaxe: int feof(FILE *fp); Esta função pode ser aplicada tanto para arquivo texto como para arquivos binários. A rotina do exemplo 15.4 lê um arquivo binário até que o final do arquivo seja encontrado. Exemplo 15.4 while (!feof(fp)) ch = getc(fp); LINGUAGEM C E/S com Arquivo 88/103 15.10 Trabalhando com arquivos As funções fopen(), getc(), putc() e fclose() constituem o conjunto mínimo de rotinas de arquivos. O programa a seguir lê caracteres do teclado e os escreve em um arquivo em disco até que o usuário digite um cifrão ($). O nome do arquivo é passado pela linha de comando. #include “stdio.h” #include “stdlib.h” void main(int argc, char *argv[]) { FILE *fp; char ch; if(argc !=2) { printf(“Voce esqueceu de entrar o nome do arquivo \n”); exit(1); } if((fp=fopen(argv[1],”w”))==NULL) { printf(“Arquivo nao pode ser aberto\n”); exit(1); } do { ch = getchar(); putc(ch,fp); } while(ch!=‘$’); fclose(fp); } O programa complementar descrito a seguir lê qualquer arquivo ASCII e mostra o conteúdo na tela. #include “stdio.h” #include “stdlib.h” void main(int argc, char *argv[]) { FILE *fp; char ch; if(argc !=2) { printf(“Voce esqueceu de entrar o nome do arquivo \n”); exit(1); } if((fp=fopen(argv[1],”r”))==NULL) { printf(“Arquivo nao pode ser aberto\n”); exit(1); } ch = getc(fp); while (ch!=EOF) { putchar(ch); ch = getc(fp); } fclose(fp); } LINGUAGEM C E/S com Arquivo 89/103 15.11 Trabalhando com Strings: fputs() e fgets() Para a gravação e leitura de strings de caractere para e de um arquivo em disco são utilizadas as funções fgets() e fputs(), respectivamente. São os seguintes os seus protótipos: Sintaxe: int fputs(const char *str, FILE *fp); char *fgets(char *str, int length, FILE *fp); A função fputs() opera como puts(), mas escreve a string na stream especificada. EOF é devolvido se ocorre um erro. A função fgets() lê uma string da stream especificada até que um caractere de nova linha seja lido ou que length-1 caracteres tenham sido lidos. Se uma nova linha é lida, ela será parte da string (diferente de gets()). A string resultante será terminada por um nulo. A função devolve um ponteiro para str se bem-sucedida ou um ponteiro nulo se ocorre um erro. O programa a seguir lê strings do teclado e escreve-as no arquivo chamado frase.dat. A condição de saída é uma linha em branco. Como gets() não armazena o caractere de nova linha, é adicionado um antes que a string seja escrita no arquivo para que o arquivo possa ser lido mais facilmente. #include “stdio.h” #include “stdlib.h” #include “string.h” void main(void) { char str[80]; FILE *fp; if((fp=fopen(“frase.dat”,”w”))==NULL) { printf(“Arquivo nao pode ser aberto\n”); exit(1); } do { printf(“entre uma string (CR para sair): \n”); gets(str); strcat(str,”\n”); fputs(str,fp); } while (*str != ‘\n”); fclose(fp); } 15.12 Funções de tratamento de arquivos Nas próximas seções serão vistas algumas funções utilizadas em operações com arquivos com buffer. 15.12.1 rewind() Esta função reposiciona o indicador de posição de arquivo no início do arquivo especificado como seu argumento. Seu protótipo é: Sintaxe: void rewind(FILE *fp); LINGUAGEM C E/S com Arquivo 90/103 O exemplo 15.5 rebobina o arquivo do programa anterior e mostrar o conteúdo do mesmo. Exemplo 15.5 #include “stdio.h” #include “stdlib.h” #include “string.h” void main(void) { char str[80]; FILE *fp; if((fp=fopen(“frase.dat”,”w”))==NULL) { printf(“Arquivo nao pode ser aberto\n”); exit(1); } do { printf(“entre uma string (CR para sair): \n”); gets(str); strcat(str,”\n”); fputs(str,fp); } while (*str != ‘\n”); rewind(fp); /* reinicializa o file pointer */ while(!feof(fp)) { fgets(str, 79, fp); printf(str); } fclose(fp); } 15.12.2 ferror() A função ferror() determina se uma operação com arquivo produziu um erro. A função ferror() tem esse protótipo: Sintaxe: int ferror(FILE *fp); Ela retorna verdadeiro se ocorreu um erro durante a última operação no arquivo; caso contrário, retorna falso. 15.12.3 remove() A função remove() apaga o arquivo especificado. Seu protótipo é: Sintaxe: int remove(char *nome_arq) Ela devolve zero caso seja bem-sucedido e um valor diferente de zero caso contrário. O exemplo 15.6 apresenta um trecho de programa que apaga o arquivo dados.dat. Exemplo 15.6 LINGUAGEM C E/S com Arquivo 91/103 if (remove(“dados.dat”)) { printf(“Arquivo nao pode ser apagado\n”); exit(1); } 15.12.4 fflush() Para se esvaziar o conteúdo de uma stream de saída, deve-se utilizar a função fflush(), cujo protótipo é mostrado a seguir: Sintaxe: int fflush(FILE *fp); Essa função escreve o conteúdo de qualquer dado existente no buffer arquivo associado a fp. Se fflush() devolve 0 para indicar sucesso; caso contrário, devolve EOF. 15.12.5 Função ftell() Esta função retorna a posição do ponteiro de um arquivo binário em relação ao seu começo. Esta função aceita um único argumento, que é o ponteiro para a estrutura FILE do arquivio. Seu protótipo é mostrado aqui: Sintaxe: long ftell (FILE *fp); Retorna um valor do tipo long, que representa o número de bytes do começo do arquivo até a posição atual. A função ftell() pode não retornar o número exato de bytes se for usada com arquivos em modo texto, devido à combinação CR/LF que é representada por um único caractere em C. 15.13 Lendo e gravando registros As funções fread() e fwrite() possibilitam uma maneira de transferir blocos de dados do disco para a memória do computador e vice-versa. A grande vantagem destes comandos é poder ler e gravar dados maiores que um byte e que formem estruturas complexas (vetor, matriz, ou um registro, ou até um vetor de registros). 15.13.1 Escrita de um bloco de dados Para se gravar um bloco de dados maiores que um byte o sistema de arquivo ANSI C fornece a função fwrite(). Seu protótipo está definido a seguir: Sintaxe: size_t fwrite(void *buffer, size_t num_bytes, size_t count, FILE *fp); LINGUAGEM C E/S com Arquivo 92/103 onde buffer é um ponteiro para uma região de memória que contém as informações que serão escritas no arquivo. O número de bytes para gravar é especificado por num_bytes. O argumento count determina quantos itens serão gravados. E, finalmente, fp é um ponteiro de arquivo para uma stream aberta anteriormente. Esta função devolve o número de itens escritos. O número retornado pode ser menor que count quando o final de arquivo for atingido ou ocorrer um erro de gravação. Exemplo 15.7 int var_int; FILE *arq; arq = fopen(“dados.dat”,”wb”); var_int = 5; fwrite(&var_int,sizeof(var_int),10,arq); 15.13.2 Leitura de um bloco de dados Para se ler um bloco de dados maiores que um byte o sistema de arquivo ANSI C fornece a função fread(). Seu protótipo está definido a seguir: Sintaxe: size_t fread(void *buffer, size_t num_bytes, size_t count, FILE *fp); onde buffer é um ponteiro para uma região de memória que receberá os dados do arquivo. O número de bytes para ler é especificado por num_bytes. O argumento count determina quantos itens serão lidos. E, finalmente, fp é um ponteiro de arquivo para uma stream aberta anteriormente. Esta função devolve o número de itens lidos. O número retornado pode ser menor que count quando o final de arquivo for atingido ou ocorrer um erro de leitura. Exemplo 15.8 int var_int; FILE *arq; arq = fopen(“dados.dat”,”rb”); fread(&var_int,sizeof(var_int),1,arq); 15.13.3 Utilizando os comandos de leitura e gravação de registros Uma das mais úteis aplicações de fread() e fwrite() envolve ler e escrever tipos de dados definidos pelo usuário, especialmente estruturas. #include “stdio.h” struct pto { int x,y; }; typedef struct pto ponto; void main() { FILE *fp; int i; ponto coord[10]; for (i=0; i < 10; i++) LINGUAGEM C E/S com Arquivo 93/103 { printf(“Coordenada x:”); scanf(“%d \n”,&coord[i].x); printf(“Coordenada y:”); scanf(“%d \n”,&coord[i].y); } if((fp=fopen(“figura.dat”,”w”))==NULL) { printf(“Arquivo nao pode ser aberto\n”); exit(1); } for (i=0; i < 10; i++) fwrite(&coord[i], sizeof(ponto),1, fp); rewind(fp); i = 0; while(!feof(fp)) { fread(&coord[i], sizeof(ponto), 1, fp); printf(“Coordenadas (x,y) = (%d,%d),coord[i].x,coord[i].y); } fclose(fp); } 15.14 Acesso aleatório Operações de leitura e escrita aleatórias podem ser executadas utilizando o sistema de E/S bufferizado com a ajuda de fseek(), que modifica o indicador de posição de arquivo. Seu protótipo é mostrado aqui: Sintaxe: int fseek (FILE *fp, long numbytes, int origem); Onde, numbytes, um inteiro longo, é o número de bytes a partir de oriem, que se tornará a nova posição corrente, e origem é uma das seguintes macros definidas em STDIO.H. Tabela 15.3 – Macros definidas em STDIO.H para as posições permitidas na função fseek(). Origem Nome da Macro Início do arquivo SEEK_SET Posição atual SEEK_CUR Final do arquivo SEEK_END A função fseek() devolve 0 se a operação for bem-sucedida e um valor diferente de zero se ocorre um erro. O exemplo 15.9 mostra a utilização da função fseek(). Este exemplo recebe pela linha de comando o deslocamento a ser realizado sempre a partir do início do arquivo (SEEK_SET). Exemplo 15.9 #include “stdio.h” #include “stdlib.h” LINGUAGEM C E/S com Arquivo 94/103 void main(int argc, char *argv[]) { FILE *fp; char ch; if(argc !=3) { printf(“Uso: SEEK nomearq byte \n”); exit(1); } if((fp=fopen(argv[1],”r”))==NULL) { printf(“Arquivo nao pode ser aberto\n”); exit(1); } if(fseek(fp, atol(argv[2]), SEEK_SET)) { printf(“erro na busca\n”); exit(1); } printf(“O byte em %ld é %c\n”, atol(argv[2]), getc(fp)); fclose(fp); } No exemplo 15.9 verifica-se a utilizção da função atol(), que tem por função converter uma string em um inteiro longo. No printf() utiliza-se o modo de formatação %ld que é para apresentação de um decimal longo ou um inteiro longo. 15.15 Comando de gravação em modo texto formatado Como extensão das funções básicas de E/S já discutidas, o sistema de E/S com buffer inclui fprintf() e fscanf(). Essas funções se comportam exatamente como printf() e scanf() exceto por operarem em arquivos. Os protótipos de fprintf() e fscanf() são Sintaxe: int fprintf(FILE *fp, const char *control_string, ...); int fscanf(FILE *fp, const char *control_string, ...); onde fp é um ponteiro de arquivo devolvido por uma chamada à fopen(). fprintf() e fscanf() direcionam suas operações de E/S para o arquivo apontado por fp. A fscanf() devolve o número de elementos lidos, convertidos e armazenados. No caso de fim de arquivo o fscanf() devolve o valor EOF. O exemplo 15.10 mostra um programa que lê uma string e um inteiro do teclado, escreve-os em um arquivo em disco e em seguida lê e mostra a informação na tela. Exemplo 15.10 #include “stdio.h” #include “stdlib.h” #include “io.h” void main(void) { FILE *fp; char s[80]; int t; if((fp=fopen(“teste”,”w”))==NULL) LINGUAGEM C E/S com Arquivo 95/103 { printf(“Arquivo nao pode ser aberto\n”); exit(1); } printf(“entre com uma string e um número: “ ); fscanf(stdin, “%s%d”, s, &t); /* le do teclado */ fprintf(fp, “%s %d”, s, t); /* escreve no arquivo */ fclose(fp); if((fp=fopen(“teste”,”w”))==NULL) { printf(“Arquivo nao pode ser aberto\n”); exit(1); } fscanf(fp, “%s%d”, s, &t); /* le do arquivo */ fprintf(stdout, “%s %d”, s, t); /* imprime na tela */ } A formatação realizada para leitura e escrita de dados pode comprometer a velocidade de processamento do programa. 15.16 Condições de erro Para determinar se um erro ocorreu utiliza-se a função ferror(), mas esta informação não basta para a solução por parte do usuário. Para isso utiliza-se a função perror() em conjunto com a função ferror(). O argumento de perror() é uma string fornecida pelo programa que normalmente é uma mensagem de erro que indica em que parte do programa ocorreu erro. Sintaxe: void perror (const char *str); Se for detectado um erro de disco, ferror() retornará um valor verdadeiro (não zero) e perror() imprimirá a seguinte mensagem Erro de Busca: Bad data A primeira parte da mensagem é fornecida pelo programa, e a segunda parte, pelo sistema operacional. 15.17 Streams Padrão Sempre que um programa é iniciado três streams padrões são abertas automaticamente: • stdin (entrada padrão - teclado); • stdout (saída padrão - tela); • stderr (saída de erro padrão - tela); • stdaux (saída auxiliar padrão - porta serial); • stdprn (impressora padrão - impressora paralela). Essas streams podem ser utilizadas normalmente para executar operações de E/S bufferizada, como mostra o exemplo 15.10 na leitura de variáveis pelo teclado. Para redirecionar as streams padrão utiliza-se a função freopen(). Sintaxe: LINGUAGEM C E/S com Arquivo 96/103 FILE *freopen(const char *nomearq, const char *modo, FILE *stream); onde nomearq é um ponteiro para o nome do arquivo que se deseja associar à stream apontada por stream. O programa do exemplo 15.2 redireciona a stdout para o arquivo chamado OUTPUT. Exemplo 15.11 #include “stdio.h” void main(void) { char str[80]; freopen(“OUTPUT”, “w”, stdout); printf(“entre com uma string: ”); gets(str); printf(str); } 15.18 Exercício: 1. Faça um programa que escreva os números de 0 a 10 em um arquivo. 2. Faça um programa que leia 11 números de um arquivo. 3. Faça um programa que verifique se o número de abre-chaves corresponde ao número de fecha- chaves de um arquivo de programa fonte C, o qual é lido pelo teclado. 4. Faça um programa que gere um novo arquivo com registros ordenados (crescente) pelo código de um arquivo, cujo nome é fornecido pelo usuário. A estrutura do arquivo é: struct reg { int cod; char nome[30]; char idade; }; 15.19 Sistema de Arquivo tipo UNIX Como C foi originalmente desenvolvida sobre o sistema operacional UNIX, ela inclui um segundo sistema de E/S com arquivos em disco que reflete as operações em disco de baixo nível do UNIX. A diferença está no fato que tanto a leitura como a gravação são realizadas sem buffer. Isto é, o programador fica com o trabalho de criar e manter o buffer de dados. Para as operações em disco utilizam-se algumas funções especiais, as quais estão descritas na tabela 15.3 Tabela 15.3 - Funções de E/S tipo UNIX sem buffer Nome Função open() Abre um arquivo em disco close() Fecha um arquivo em disco read() Lê um buffer de dados write() Escreve um buffer de dados lseek() Move ao byte especificado em um arquivo tell() Devolve o valor atual do indicador de posição de arquivo unlink() Apaga um arquivo do diretório LINGUAGEM C E/S com Arquivo 97/103 O arquivo-cabeçalho usado pelo sistema de arquivo tipo UNIX é chamado IO.H em muitas implementações. Em algumas funções é necessário a inclusão do cabeçalho FNCTL.H (definição de algumas macros). 15.19.1 open() Ao contrário do sistema de arquivos com buffer, o sistema de baixo nível (sem buffer) não utiliza ponteiros de arquivo do tipo FILE, mas descritores de arquivo do tipo int. O protótipo para open() é: Sintaxe: int open(const char *nomearq, int modo); onde nomearq é qualquer nome de arquivo válido e modo é uma das seguintes macros que são definidas no arquivo-cabeçalho FNCTL.H. Tabela 15.4 Modos de abertura de arquivos em baixo nível Modo Efeito O_RDONLY Abre um arquivo somente para leitura O_WRONLY Abre um arquivo somente para escrita O_RDWR Abre um arquivo para leitura/gravação O_APPEND coloca o ponteiro de arquivo no fim dele O_CREAT cria um novo arquivo para gravação (não faz efeito se o arquivo já existe) O_TRUNC Abre e trunca umarquivo existente para tamanho 0 O_BINARY Abre um arquivo em modo binário O_TEXT Abre um arquivo em modo texto Os modos de abertura de arquivo em baixo nível são mostrados na tabela 16.5. A existência destas macros dependem do compilador, isto é, nem todas podem existir. Algumas das macros listadas são mutuamente exclusivas, por exemplo, não pode abrir o arquivo para somente leitura e somente gravação ao mesmo tempo. Quando dois ou mais macros são utilizadas ao mesmo tempo, devem ser combinadas usando-se o operador bit-a-bit OR (|). Exemplo 15.12 #include <io.h> #include <fnctl.h> main() { int i; /* identificador do arquivo */ char buff[512]; /* definição do buffer */ if((i=open(“teste.txt”,O_RDONLY | O_BINARY)) < 0) perror(“Não posso abrir arquivo:”); LINGUAGEM C E/S com Arquivo 98/103 ... } No exemplo 16.12, é mostrado a abertura de um arquivo somente para leitura (O_RDONLY) e binário (O_BINARY). O comando open() devolve um inteiro para i como descritor do arquivo para futuras operações no mesmo. 15.19.2 creat() Se o compilador não permitir criar um arquivo novo com a função open() ou se quer garantir a portabilidade, deve-se utilizar a função creat() para criar um arquivo novo para operações de escrita. A sintaxe para creat() é: Sintaxe: int creat(const char *nomearq, int modo); Os argumentos desta função seguem a mesma sintaxe da função open(). 15.19.3 close() A função close() libera o identificador ou descritor do arquivo para que ele possa ser reutilizado por outro arquivo. Sintaxe: int close(int descritor_de_arquivo); O descritor_de_arquivos deve ser um descritor de arquivos válido, previamente obtido através de uma chamada open() ou creat(). A função close() devolve -1 se for incapaz de fechar o arquivo, e 0 caso contrário. 15.19.4 read() e write() Uma vez o arquivo aberto pela função open(), para escrever alguma informação utiliza-se a função write(). O protótipo para a função write() é Sintaxe: int write (int fd, const void *buf, unsigned size); Ao chamar a função write() são escritos size caracteres no arquivo em disco especificado por fd do buffer apontado por buf. O buffer pode ser uma região alocada na memória ou uma variável. A função write() devolve o número de bytes escritos no caso de uma operação bem-sucedida. No caso de erro a função devolve EOF. Para a leitura de dados tem-se a função read(). Seu protótipo é: Sintaxe: LINGUAGEM C E/S com Arquivo 99/103 int read (int fd, const void *buf, unsigned size); onde fd, buf e size são os mesmos parâmetrosde write(), exceto pro read() colocar os dados do arquivo em buf. No caso da operação ser bem-sucedido é devolvido o número de bytes lidos. Caso contrário, ela devolve 0 se o final do arquivo for ultrapassado e -1 se ocorrerem erros. O exemplo 15.13 mostra um programa que utiliza alguns aspectos da E/S sem buffer. Este programa lê linhas de texto, escreve-as em um arquivo em disco e as lê de volta. Exemplo 15.13 #include “stdio.h” #include “io.h” #include “stlib.h” #include “string.h” #include “fnctl.h” #define BUF_SIZE 128 void input(char *buf, int fd1); void display(char *buf, int fd2); void main(void) { char buf[BUF_SIZE]; int fd1,fd2; if((fd1=open(“test”,O_WRONLY))==-1) { printf(“Arquivo não pode ser aberto\n”); exit(1); } input(buf,fd1); close(fd1); if((fd1=open(“test”,O_RDONLY))==-1) { printf(“Arquivo não pode ser aberto\n”); exit(1); } display(buf,fd2); close(fd2); } /* Insere texto */ void input(char *buf, int fd1) { register int t; do { for(t=0; t<BUF_SIZE; t++) buf[t] = ‘\0’; gets(buf); if (write(fd1, buf, BUF_SIZE) != BUF_SIZE) { printf(“Erro de escrita \n”); exit(1); } } while (strcmp(buf, “sair”); } /* Mostra o arquivo */ void display(char *buf, int fd2); { for(;;) { if (read(fd2, buf, BUF_SIZE) == 0) return; LINGUAGEM C E/S com Arquivo 100/103 printf(“%s\n”, buf); } } 15.19.5 unlink() Esta função é utilizada para excluir um arquivo. Seu protótipo é Sintaxe: int unlink(const char *nomearq); onde nomearq é um ponteiro de caracteres para algum nome válido de arquivo. A função unlink() devolve -1 caso seja incapaz de excluir o arquivo. 15.19.6 lseek() e tell() Para o acesso aleatório as funções lseek() e tell() são as equivalentes em baixo-nível das funções fseek() e ftell() discutidas em alto-nível. Os protótipos das duas funções são Sintaxe: long lseek(int fd, long offset, int origem); long tell(int fd); onde os parâmetros são idênticos às esquivalentes em alto-nível, exceto o primeiro argumento que é um identificador de arquivo e não um ponteiro FILE. 15.19.7 Funções para manipulação de buffers Para trabalhar com buffers utilizam-se algumas funções especializadas que são independentes de tipo. A função memchr() procura, no vetor apontado por buffer, pela primeira ocorrência de ch nos primeiros count caracteres. Devolve um ponteiro para a primeira ocorrência de ch em buffer ou um ponteiro nulo se ch não for encontrado. O protótipo da função é Sintaxe: void *memchr(const void*buffer, int ch, size_t count); Exemplo 15.14 #include “stdio.h” #include “string.h” void main(void) { char *p; p = memchr(“isto e um teste”. ´ ´,14); printf(p); } LINGUAGEM C E/S com Arquivo 101/103 A função memcmp() compara os primeiros count coracteres das matrizes apontadas por buf1 e buf2. O valor devolvido segue os valores da função strcmp(). O protótipo da função é Sintaxe: int memcmp(const void*buf1, const void*buf2, size_t count); A função memcpy() copia os primeiros count coracteres do vetor origem para o vetor apontado por destino. Ela devolve um ponteiro para destino. O protótipo da função é Sintaxe: void *memcpy(void*destino, const void*origem, size_t count); Exemplo 15.15 #include “stdio.h” #include “string.h” #define SIZE 80 void main(void) { char buf1[SIZE], buf2[SIZE]; strcpy(buf1, “Quando, no curso do ...”); memcpy(buf2, buf1, SIZE); printf(buf2); } A função memmove() copia count caracteres do vetor apontado por origem para o vetor apontado por destino. Se as matrizes se sobrepõem, a cópia ocorrerá corretamente, colocando o conteúdo correto em destino, porém origem será modificado. Ela devolve um ponteiro para destino. O protótipo da função é Sintaxe: void *memmove(void*destino, const void*origem, size_t count); Exemplo 15.16 #include “stdio.h” #include “string.h” #define SIZE 80 void main(void) { char buf1[SIZE], buf2[SIZE]; strcpy(buf1, “Quando, no curso do ...”); memmove(buf2, buf1, SIZE); printf(buf2); } A função memset() copia o byte menos significativo de ch nos primeiros count caracteres do vetor apontado por buf. Ela devolve buf. é muito utilizado na inicialização de uma região de memória com algum valor conhecido. O protótipo da função é Sintaxe: void *memset(void*buf, int ch, size_t count); Exemplo 15.17 /* Inicaliza com nulo os 100 primeiros bytes */ memset(buf, ’\0’, 100); /*do vetor apontado por buf */ LINGUAGEM C E/S com Arquivo 102/103 /* Inicializa com X os 10 primeiros bytes */ memset(buf, ’X’, 10); printf(buf); LINGUAGEM C Bibliografia 103/103 16. Bibliografia BERRY, J. Programando em C++. São Paulo: Makron Books, 1991. ECKEL, B. C++. São Paulo: McGraw-Hill, 1991. ELLIS, M. A. et alli C++: Manual de referência completo. Rio de Janeiro: Campus, 1993. IBPI. Dominando a Linguagem C. Rio de Janeiro: IBPI, 1993. MIZRAHI, V. V. Treinamento em Linguagem C. São Paulo: McGraw-Hill, 1990. PAPPAS, C. H.; MURRAY, W. Turbo C++ completo e total. São Paulo: McGraw-Hill, 1991. SCHILDT, H. C Completo e total. São Paulo: McGraw-Hill, 1991.