Prévia do material em texto
1 1. INTRODUÇÃO 1.1. Características da linguagem • linguagem de médio nível; • linguagem estruturada; • versátil (aplicação em várias áreas); • portável; • código eficiente; • compilação condicional; • compilação separada (funções em vários arquivos); • linguagem voltada para o programador. 1.2. Histórico • BCPL ⇒ B ⇒ C ⇒ C++ • C - Kernighan e Ritchie (K & R) - 1971 • C ANSI (American National Standards Institute) - 1989 1.3. Características de um programa em C • um programa em C é composto por pelo menos uma função, a função main; • todo programa em C começa a sua execução a partir da função main; • normalmente, um programa em C é uma coleção de chamadas de funções de pequeno tamanho; • toda função tem um tipo; • as palavras reservadas e funções da biblioteca do C são sempre escritas com letras minúsculas. 1.4. Formato de um programa em C definições função_1 função_2 ... função_n função_main Obs.: A ordem das funções dentro de um programa pode ser qualquer. 1.5. Exemplo de um programa # include <stdio.h> /* instrução para o pré-processador */ void main ( ) /* início da função main */ { /* início de bloco */ int i ; /* declaração de variável */ i = 10 ; /* inicializa */ printf ( " i = %d \ n", i ) ; /* chama função de impressão */ } /* fim de bloco */ 2 Obs.: • toda instrução termina por ';' • o protótipo da função printf está em stdio.h (standard IO) 1.6. O conceito de protótipo de uma função • Protótipo em C • Protótipo em C ANSI 2. IDENTIFICADORES São usados em nomes de funções e variáveis. ANSI: 31 caracteres para variáveis locais 6 para funções e variáveis globais A maioria dos compiladores reconhece 31 caracteres tanto para variáveis quanto para funções. • devem começar por letra ou ' _ '; • normalmente se usam letras minúsculas; • não podem ser iguais às palavras reservadas; • minúsculas diferentes de maiúsculas: ABC ≠ Abc ≠ ABc ≠ abc 3. TIPOS DE DADOS PARA VARIÁVEIS E FUNÇÕES 3.1. Standard (já definidos na linguagem) Tipo Nome Tamanho Faixa em Bytes em Bits caracter char 1 8 0 a 255 inteiro int 2 16 -32768 a 32767 int 4 32 -2.147.483.648 a 2.147.483.647 real float 4 32 6 dígitos double 8 64 12 dígitos nada void 0 0 sem valor Obs.: O tipo inteiro possui o tamanho da palavra da CPU. 3.2. Modificadores de tipo Alteram o significado do tipo standard. • signed (com sinal) • unsigned (sem sinal) • long (passa inteiro de 2 bytes para 4 bytes) • short (passa inteiro de 4 bytes para 2 bytes) 3 Se tipo int possuir 2 bytes: long int = long ⇒ 4 bytes short int = int ⇒ 2 bytes Ex.: signed char ⇒ -128 a 127 unsigned int ⇒ 0 a 65535 4. DECLARACÃO DE VARIÁVEIS Evidentemente, as variáveis devem ser declaradas antes de serem usadas. Formato: <tipo> <lista de variáveis>; Ex.: int a, b, c; float soma; double somatorio, raiz_quadrada; char sim, nao; As variáveis podem ser declaradas dentro de qualquer bloco: { int x, y; comandos; } As variáveis podem ser locais, globais e parâmetros formais 4.1. Variáveis locais São aquelas declaradas dentro de um bloco e só são reconhecidas dentro deste bloco. Exemplo 1 Exemplo 2 # include <stdio.h> void main ( ) { int i, j ; i = 2 ; j = 2 * i ; printf ( "0 dobro de %d = %d \ n", i, j ); } No exemplo acima, 'i' e 'j' são variáveis locais da função "main". void f1 ( ) { int x ; x = 5 ; . . . if ( a > 10 ) { int x, y ; x = 20 ; y = 40 ; . . . } } Neste exemplo, a variável 'x' tem o valor 5 quando sair do bloco mais interno. 4 4.2. Variáveis globais São reconhecidas por todas as funções de um programa ou de um arquivo. Ex.: # include <stdio.h> int cont; void f ( void ) ; void main ( ) { cont = 100 ; printf ("cont em main = %d \ n", cont ) ; cont = cont + 1 ; f ( ) ; } void f ( ) { printf ( "cont em f = %d \ n", cont ) ; } 4.3. Parâmetros formais São os parâmetros da declaração função e considerados como variáveis locais desta função. Obs.: O C só passa parâmetros por valor, isto é, o que é passado para a função é uma cópia do conteúdo dos parâmetros efetivos (que aparecem na chamada para execução da função). Ex.: int soma ( int a, int b ) { return ( a + b ); } No exemplo, 'a' e 'b' são parâmetros formais e a função "soma" poderia ser assim chamada: j = soma ( 2, 3 ) ; x = soma ( a, b ) ; 4.4. Modificadores de classe de armazenamento Indicam como as variáveis devem ser armazenadas. Exemplo do mapa da memória de um programa em C em um equipamento genérico: Área de Código Área de Variáveis Estáticas e Globais Stack Heap 5 A área de stack possue entre 2 a 4 Kbytes e armazena as variáveis locais. Heap é o que sobra da memória e é onde são feitas as alocações dinâmicas. 4.4.1. Variáveis automáticas – auto São as variáveis locais declaradas normalmente. Ex.: int x ; <=> auto int x ; A palavra auto não é necessária. 4.4.2. Variáveis estáticas - static As variáveis estáticas são armazenadas na área de variáveis globais e por isto, não perde o seu valor dentro da função ou arquivo. Variável estática (ou global) é inicializada com 0 (zero). Este tipo de modificador é mais empregado para variáveis de grandes dimensões (vetores, matrizes, strings etc.). Quando uma função é definida como static, ela só pode ser ativada por uma função que esteja no mesmo arquivo. 4.4.3. Variáveis externas - extern São as variáveis globais definidas em arquivos diferentes Ex.: arquivo 1: int x, y ; void main ( ) { y = 30 ; x = 10 ; f ( ) ; . . . } arquivo 2: . . . extern int x, y ; void f ( ) { . . . printf ( " %d %d \ n ", x, y ) ; . . . } 4.4.4. Variáveis register 6 Neste caso, a variável é colocada em um dos registradores da CPU. Ex.: register int i, j ; Deve ser bem escolhida, isto é, deve ser mais usada em casos nos quais a velocidade é importante, como, por exemplo, em variáveis que controlam loops. 5. FUNCÕES printf e scanf Ao contrário de outras linguagens, no C as operaçães de leitura e impressão, tanto na tela quanto em arquivos, é totalmente feita por funções. Os protótipos destas funções se encontram no arquivo stdio.h, o qual deve sempre ser incluído quando forem usadas funções de IO (entrada e saída). 5.1. printf Principal função de impressão no vídeo. Formato Geral: int printf ( formato, argumentos ) ; A função printf retoma o número de caracteres impressos. Obs.: • Em C, o valor de retorno de uma função pode ser ignorado. • Em C, as funções podem ter um número variável de parâmetros. Ex.: printf ( " \ n FIM \ n " ) ; Principais formatos de impressão: • %d - inteiro • %c - caracter • %f - float • %ld - long int • %lf - double • %s - string • %e - notação científica A saída pode ser formatada através do tamanho do campo: • %3d - inteiro com 3 dígitos • %-3d - - inteiro com 3 dígitos ajustados à esquerda do campo• %7.2f - float com dois dígitos após o ponto decimal 5.2. scanf Principal função de leitura do teclado Formato: int scanf ( formato, parâmetros ) ; Retoma o número de variáveis lidas corretamente ou EOF em caso de erro grave. 7 Principais formatos: • %d - inteiro • %c - caracter • %f - float • %ld - long int • %lf - double • %s - string Para a função scanf, o branco é considerado delimitador. Ex: scanf ( " %d %d ", &a, &b ) ; scanf ( " %d , %d ", &a, &b ) ; 5.3. Exercício: Ler dois números e imprimir a soma. # include < stdio.h > void main ( ) { int a, b, c ; printf ( " Entre 2 numeros: " ) ; scanf ( " %d %d ", &a, &b ) ; c = a + b ; printf ( " Soma = %d \n ", c ) ; /* printf ( " Soma = %d \n ", a + b ) ; */ } 6. INSTRUÇÕES DE CONTROLE 6.1. while Formato Geral: while (condição) bloco; Executa o bloco enquanto a condição for verdadeira. Obs.: • Em C, verdadeiro é tudo o que for diferente de zero. • Se o bloco possuir mais de uma instrução é necessário { }. Ex.: Imprimir a soma dos números de 1 a 5 com while 8 Solução 1 Solução 2: # include < stdio.h > void main ( ) { int i, soma ; i = 1 ; soma = 0 ; while ( i <= 5 ) { soma = soma + i ; i = i + 1 ; } printf ( " \n Soma = %d \n ", soma ) ; } # include < stdio.h > void main ( ) { int i = 1, soma = 0 ; while ( i <= 5 ) { soma += i ; i ++ ; } printf ( " \n Soma = %d \n ", soma ) ; } Obs.: • Incremento usando pré-operador (++ i ⇒ incrementa antes de usar a variável i) ou pós- operador (i ++ ⇒ incrementa depois de usar a variável i): supondo a= 1: para b = ++ a ; ⇒ b = 2 e a = 2 para b = a ++ ; ⇒ b = 1 e a = 2 • Forma reduzida de var = var op exp ; ⇒ var op = exp ; x = x + 5 ; ⇒ x += 5 ; x = x / ( a + b ) ; ⇒ x /= a + b ; 6.2. for Formato Geral: for ( inicialização; condição; incremento ) bloco; Exemplo: Imprimir os números de 1 a 10 # include < stdio.h > void main ( ) { int i ; for ( i = 1; i <= 10; i ++ ) printf ( " %d \n ", i ) ; } Obs.: Todos os campos do for são opcionais. # include < stdio.h > void main ( ) { int i = 1 ; for ( ; i <= 10 ; ) printf ( " %d \n ", i ++ ) ; } Exercício: Calcular o fatorial de um número inteiro e não negativo lido usando for. Solução 1: # include < stdio.h > 9 void main ( ) { int n, i, fator = 1 ; printf ( " Entre o numero: " ) ; scanf (" %d ", &n ) ; for ( i = 1; i <= n; i ++ ) /* poderia ser ainda: */ fator *= i ; /* for (i = 1, fator = 1; i <= n; fator *= i ++ ) ; */ printf ( " Fatorial de %d = %d \n ", n, fator ) ; } Solução 2: # include < stdio.h > void main ( ) { int n, i, fat ; printf ( " Entre o numero: " ) ; scanf ( " %d ", &n ) ; i = n ; for ( fat = 1; n > 1; n -- ) fat *= n ; printf ( " Fatorial de %d = %d \n ", i, fat ) ; } 6.3. do while Formato Geral: do bloco; while (condição); Repete o bloco enquanto a condição for verdadeira. Ex.: Imprimir os números de 1 a 10 # include < stdio.h > void main ( ) { int i = 1; do { printf (" %d \n ", i ); i ++ ; } while (i <= 10); } 6.4. if e derivados Formatos: 10 if (teste) bloco; if (teste) bloco1; else bloco2; if (teste1) bloco1; else if (teste2) bloco2; else if (teste3) bloco3; else bloco4; Exercício: Ler 2 números e indicar o maior # include < stdio.h > void main ( ) { int a, b ; printf ( " Entre 2 numeros: " ) ; scanf ( " %d %d ", &a, &b ) ; if ( a = = b ) /* teste de igualdade é com = = */ printf ( " Sao iguais \n " ) ; else if ( a > b ) printf ( " O primeiro é maior \n " ) ; else printf ( " O segundo é maior \n " ) ; } Exercício: Ler uma série de números reais e indicar o maior e o menor. # include < stdio.h > void main ( ) { float min, max, x ; printf ( " Entre os numeros: \n " ) ; scanf ( " %f ", &x ) ; min = max = x ; /* atribuição múltipla */ while ( scanf ( " %f ", &x ) = = 1 ) if ( min > x ) min = x ; else if ( max < x ) max = x ; printf ( " O maior é: %f \n ", max ) ; printf ( " O menor é: %f \n ", min ) ; } 6.5. if com ? Útil para pequenos if’s. if ( a = = 10 ) Poderia ser: b = ( a = = 10 ) ? 20 : 30 ; b = 20; ⇓ ⇓ else V F 11 b = 30; 6.6. switch Substitui grandes if’s. switch ( exp ) { case ctel : bloco l ; break ; case cte2 : bloco 2 ; break ; case cte3 : bloco 3 ; break ; . . . . . . case cten : bloco n ; break ; default : bloco ; break ; /* não é necessário break aqui */ } Obs.: A expressão 'exp' deve fornecer resultado inteiro. 6.7. continue Passa o controle para o próximo incremento em um for, ignorando as instruções restantes dentro do bloco do for. . . . for ( i = 0 ; i < 10 ; i ++ ) { if ( i = = 0 ) continue ; /* evita divisão por zero */ printf ( " %f \n ", ( float ) 10 / i ) ; } . . . Obs.: O cast ( float ) 10 / i converte para real. Ex.: ( float ) 10 / 3 ⇒ 3.333... ( float ) (10 / 3) ⇒ 3.0 6.8. break Interrompe a execução de um for, while, do . . . while ou switch. O fluxo de execução do programa é desviado para primeira instrução fora do bloco que possui o break. 12 Ex.: . . . while ( 1 ) /* loop infinito */ { scanf ( " %d ", &a ) ; if ( a != 0 ) processa ( ) ; else break; } . . . Obs.: Deve-se usar um break por loop, quando embutidos. 6.9. goto label Desvia o fluxo de execução do programa para um label em outro ponto do programa. . . . goto Label_1 ; . . . Label_l : . . . Obs.: Para label sem instruções deve-se usar “ ; ” no final: Erro_Fatal : ; 6.10. Exercícios 6.10.1. Fazer um programa que simule uma calculadora com as quatro operações e ainda, teste divisão por zero e sinal de operação inválido. # define FALSE 0 /* definição de constantes */ # define TRUE 1 # include < stdio.h > void main ( ) { float vl, v2, res ; char op ; int erro = FALSE ; /* usa int como variável lógica */ printf ( " Entre com a expressão : \n " ) ; scanf ( " %f %c %f ", &vl, &op, &v2 ) ; /* le a expressão */ switch ( op ) { case ‘+’ : res = vl + v2 ; 13 break ; case ‘-’ : res, = vl - v2 ;break ; case ‘*’ : res = vl * v2 ; break ; case ‘/ ’ : if ( v2 = = 0.0 ) { printf ( " \n Erro: Divisão por zero \n " ) ; erro = TRUE ; } else res = vl / v2 ; break ; default : erro = TRUE ; printf ( " \n Sinal inválido \n " ) ; break ; /* este break não é necessário */ } if ( !erro ) /* não precisa fazer erro = = TRUE */ printf ( " \n O resultado é : %10.4f \n ", res ) ; } 6.10.2. Fazer um programa que leia um valor n inteiro e, em seguida, leia n valores reais, calcule e imprima o quadrado de cada um. # include < stdio.h > void main ( ) { float x ; int n ; printf ( " Numero de valores a serem lidos : " ) ; scanf ( " %d ", &n ) ; while ( n-- ) { printf ( " Valor : " ) ; scanf ( " %f ", &x ) ; printf ( " Quadrado de %f = %f \n ", x, x * x ) ; } } Obs.: Redirecionainento para entrada e/ou saída, usando arquivos DOS ou UNIX: na chamada do programa indicar um arquivo de entrada e/ou um de saída. Por exemplo, no DOS: C:\> nome_do_programa <arq_entrada >arq_saida 14 7. FUNÇÕES Formato Geral: tipo nome_função ( parâmetros ) { declaração de variáveis locais ; corpo da função ; { • tipo é o tipo do valor que a função retorna; • se uma função não retoma nada, o seu tipo é void; • os parâmetros são sempre passados por valor, isto é, uma cópia do valor do parâmetro efetivo é passado para a função; • para arrays (vetores e matrizes) é passado o valor do endereço da sua primeira posição; • é aconselhável usar sempre o protótipo da função; • variáveis char são transformadas em int e variáveis float são transformadas em double. Obs.: O formato da função varia entre o padrão K & R e o ANSI, sendo que o padrão K & R pode ser usado no ANSI. Formato K & R Formato ANSI int soma ( a, b ) int a, b ; { return ( a + b ) ; } int soma ( int a, int b ) { return ( a + b ) ; { O valor retornado por uma função pode ser ou não utilizado, como por exemplo na função printf ( ). Pode-se retornar de qualquer ponto do corpo da função, bastando para isto usar a instrução return. Exercício: Fazer um programa para ler um número e imprimir o seu fatorial utilizando uma função para leitura do número, uma para o cálculo do fatorial e uma para impressão do resultado. # include < stdio.h > long fatorial ( long ) ; long leval ( void ) ; void printfat ( long ) ; void main ( ) { long fat, val ; val = leval ( ) ; 15 fat = fatorial ( val ) ; printfat ( fat ) ; } long leval ( ) { long x ; printf ( " Entre o valor a ser calculado : " ) ; scanf ( " %ld ", &x ) ; return ( x ) ; } void printfat ( long y ) { printf ( " Fatorial = %ld \n ", y ) ; } long fatorial ( long a ) { long fator = 1 ; long i ; for ( i = 1; i <= a; i++ ) fator *= i ; return ( fator ) ; } 8. OPERADORES Um operador é um caracter ou grupo de caracteres que causará uma manipulação matemática ou lógica. 16 Em C, os operadores podem ser aritméticos, relacionais, lógicos e entre bits. 8.1. aritméticos + => adição - => subtração e menos unário * => multiplicação / => divisão % => resto divisão -- => decremento ++ => incremento += => soma -= => subtração *= => multiplicação /= => divisão 8.2. lógicos ou boleanos && => e || => ou ! => não 8.3. relacionais > => maior < => menor = = => igual != => diferente >= => maior ou igual <= => menor ou igual 8.4. entre bits & => e | => ou ∧ => ou exclusivo ~ => complemento (NOT) >> => deslocamento para direita << => deslocamento para esquerda 8.5. outros operadores * => retorna o valor contido em um endereço & => retoma o endereço de uma variável sizeof => retoma o tamanho de uma variável ou de um tipo ? => avalia se uma expressão é verdadeira , => equivalente a “faça isto e isto e isto . . .” . => membro de estrutura ou união → => ponteiro para membro de estrutura ou união ( type ) => cast - altera tipo do dado ou da expressão 8.6. precedência (da maior para a menor) ( ) [ ] -> ! ~ ++ - - (type) * & sizeof * / % + - << >> < <= > >= = = != & ∧ | && || ? = += -= *= /= , 9. ARRAYS UNIDIMENSIONAIS ( VETORES ) Definição de vetores unidirnensionais: 17 <tipo> <nome> [ <tam> ] ; Exemplo: int vet [ 5 ] ; ⇒ define vet como um vetor inteiro de 5 posições, sendo que os índices variam de 0 (zero) a 4, isto é, a posição do primeiro elemento é vet [ 0 ] e do último é vet [ 4 ]. Exemplos de acesso: int vet [ 5 ] ; . . . vet [ 0 ] = 10 ; vet [ 1 ] = 20 ; vet [ 2 ] = 40 ; . . . for ( i = 0; i < 5; i++ ) vet [ i ] = 0 ; /* zera o vetor */ . . . Inicialização na definição: para vetor local: static int vet [ ] = { 1, 3, 5 } ; ⇒ assume que o vetor vet possui 3 elementos static float vet [ 10 ] ; ⇒ assume que o vetor vet tem 10 elementos, os quais serão inicializados com zero (0) static int vet [ 5 ] = { 1, 2, 3 } ; ⇒ assume que o vetor vet tem 5 elementos, sendo que o quarto e o quinto serão inicializados com zero (0) para vetor global: int vet [ ] = { 10, 20, 40, 50 } ; assume que o vetor vet tem 4 elementos Obs.: • Quando um vetor 'extern' ou 'static' for inicializado sem dimensão, a dimensão será a do número de valores da inicialização. • Caso seja fornecida a dimensão e faltem elementos na inicialização, os elementos não inicializados passam a valer zero. Exercício: Fazer uma programa que leia o número de componentes (n <= 100) e os n componentes de um vetor de double e retome o seu maior elemento. # include < stdio.h > double maxval ( double [ ], int ) ; /* Os protótipos são obrigatórios quando */ void levet ( double [ ], int ) ; /* o corpo da função vem depois de sua */ /* chamada */ void main ( ) { double vet [ 100 ] ; int i, n ; 18 printf ( " Numero de elementos do vetor : " ) ; scanf ( " %d ", &n ) ; levet ( vet, n ) ; printf ( " Maior valor = %lf ", maxval ( vet, n ) ) ; } void levet ( double v [ ], int n ) { /* double v [ ] ; não precisa dar a dimensão */ int i ; double aux ; printf ( " Entre os elementos do vetor: " ) ; for ( i = 0; i < n; i++ ) { scanf ( " %lf ", &aux ) ; v [ i ] = aux ; /* poderia ter lido v [ i ] diretamente na scanf */ } } double maxval ( double v [ ], int n ) { int i ; double maior ; maior = v [ 0 ] ; /* maior começa como sendo o primeiro */ for ( i = 1; i < n; i++ ) if ( v [ i ] > maior ) maior = v [ i ] ; return ( maior ) ; } 9.1. Strings São arrays de caracteres. Obs.: Em C, toda string acaba com o caracter '\0'. Definição de um vetor de caracteres: char str [ 7 ] ; A declaração acima, define str como sendo um vetor de 6 caracteres mais o caracter de final de string. Exemplo: char str [ 7 ] ; str [ 0 ] = 'C' ; str [ l ] = 'a' ; str [ 2 ] = 'r' ;19 str [ 3 ] = '1' ; str [ 4 ] = 'o' ; str [ 5 ] = 's' ; str [ 6 ] = '\0' ; Na memória ficaria: C 0 a 1 r 2 l 3 o 4 s 5 \0 6 Pode-se inicializar uma string de duas formas: 1. Como um vetor: static char str [ ] = { 'C', 'a', 'r', '1', 'o', 's', '\0' } ; assume 6 caracteres mais '\0' = 7 2. Deixar que o compilador inicialize: static char str [ ] = "Carlos" ; Obs.: Não é possível a atribuição direta: char str [ 7 ] ; str = "Carlos" ; /* Erro: não vale a atribuição */ Ex.: Fazer um programa para ler um nome e dar um bom dia para o dono do nome. Solução 1: # include < stdio.h > # define TAM 100 void main ( ) { char linha [ TAM ] ; int c, i ; printf ( " \n Qual é o seu nome ? " ) ; for ( i = 0; ( c = getchar ( ) ) != '\n'; i++ ) linha [ i ] = c ; linha [ i ] = '\0' ; printf ( " Tenha um bom dia " ) ; for ( i = 0; linha [ i ] != '\0'; i++ ) putchar ( linha [ i ] ) ; putchar ( '\n' ) ; } Obs.: A função getchar retorna um caracter do teclado e a função putchar coloca um caracter no vídeo. Estas funções, bem como as outras de IO podem ser redirecionadas. Em caso de erro, a função getchar retorna EOF. Solução 2: # include < stdio.h > 20 # define TAM 100 void main ( ) { char linha [ TAM ] ; printf ( " \n Qual é o seu nome ? " ) ; gets ( linha ) ; printf ( " \n Tenha um bom dia " ) ; puts ( linha ) ; } Obs.: • A função gets tem por finalidade ler uma string e a puts imprimir uma string. • A função puts fornece automaticamente uma mudança de linha após a impressão. • Não é aconselhável usar getchar ou gets após o scanf, pois ocorrerá problema na leitura do Enter ('\n' ). • Para ler um número inteiro com a gets, pode-se usar: . . . char buf [10] ; int n ; printf ( " Valor de n = " ) ; gets ( buf ) ; n = atoi ( buf ) ; /* atoi transforma um número de string para int */ . . . Formatos: char* gets ( char* buf ) ; retorna ponteiro para a string lida ou NULL em caso de erro. int puts ( char* buf ) ; retoma '\n' se imprimiu corretamente ou EOF em caso de erro. Obs.: Para atribuição de strings, deve-se usar a função strcpy, que possui o formato: char* strcpy ( char* destino, char* origem ); Exemplo: strcpy ( str, "casa" ); str passa a conter "casa" Obs.: Para saber o tamanho de uma string, usa-se a função strlen que possui o formato: int strlen ( char* st ) ; Exercício: Construir a função strlen. Solução 1: int strlen ( char cadeia [ ] ) { 21 int len = 0, i = 0 ; while ( cadeia [ i ] != '\0' ) { ++len ; ++i ; } return ( len ) ; } Solução 2: int strlen ( char cadeia [ ] ) { int i ; for ( i = 0; cadeia [ i ] != '\0'; i++ ) ; /* bloco nulo */ return ( i ) ; } Exercício: Construir a função stringcpy Solução 1: void stringcpy ( char [ ], char [ ] ) ; void stringcpy ( char sl [ ], char s2 [ ] ) { int i ; for ( i = 0; s2 [ i ] != '\0'; i++ ) sl [ i ] = s2 [ i ] ; sl [ i ] = '\0' ; } Solução 2: void stringcpy ( char sl [ ], char s2 [ ] ) { int i = 0 ; while ( ( sl [ i ] = s2 [ i ] ) != '\0' ) i++ ; } Exercício: Desenvolver um programa para ler um nome e imprimir o número de caracteres deste nome (usar strlen). void main ( ) { int tam ; 22 char nome [100] ; printf ( " Entre seu nome " ) ; gets ( nome ) ; tam = strlen ( nome ) ; printf ( " Seu nome tem %d caracteres \n ", tam ) ; } 10. ARRAYS BIDIMENSIONAIS (MATRIZES BIDIMENSIONAIS) Formato: <tipo> <nome> [ <d1> ] [ <d2> ] ; onde: d1 ⇒ número de linhas (varia de 0 a d1 - 1) d2 ⇒ número de colunas (varia de 0 a d2 - 1) Exemplo: int b [3] [5] ; ⇒ 3 linhas e 5 colunas Inicialização na declaração: static int b [ ] [3] = { 1, 1, 1, 2, 2, 2, 3, 3, 3 }; static int b [ ] [3] = { {l, 1, 1}, {2, 2, 2}, {3, 3, 3} } Obs.: A inicialização também pode ser feita de forma esparsa. Por exemplo: static int b [3] [3] = { {1, 1}, {1} }; A matriz gerada pela inicialização acima será: 1 1 0 1 0 0 0 0 0 Deve-se notar que para este tipo de inicialização é necessário que se forneça o número de linhas e de colunas. Obs.: O armazenamento em C é feito por linhas. Passagem de um array bidimensional para uma função: ao passar uma matriz bidimensional, a única dimensão fixa é o número de colunas (ao contrário do FORTRAN que fixa o número de linhas). Exemplo: Dado: void main ( ) { 23 int mat [10] [20] ; . . . f ( mat, 10, 20 ) ; . . . } A função f ( ) poderia receber a matriz de duas formas: void f ( int mat [10] [20], int lin, int col ) ou void f ( int mat [ ][20], int lin, int col ) { . . . } Obs.: Uma matriz bidimensional passada como parâmetro tem que apresentar, obrigatoriamente, o número máximo de colunas. Exemplo para zerar uma matriz: void zera ( float mat [ ] [20], int, int ) ; /* protótipo */ void main ( ) { float mat [10] [20] ; zera ( mat, 10, 20 ) ; } void zera ( float mat [ ] [20], int lin, int col ) { register int i, j ; for ( i = 0; i < lin; i++ ) for ( j = 0; j < col; j++ ) mat [ i ] [ j ] = 0.0 ; } Exercício: Desenvolver uma função para retornar o menor elemento da diagonal principal de uma matriz 15 x 15 de elementos do tipo double. double menordp ( double mat [ ] [15] ) { int i, menor = mat [0] [0]; for ( i = 1; i < 15; i++ ) if ( mat [ i ] [ i ] < menor ) menor = mat [ i ] [ i ]; return ( menor ); } 11. PRIMEIRA LISTA DE EXERCÍCIOS 1. Desenvolver um programa que informe o número de vezes que cada dígito aparece em um arquivo texto, usando redirecionamento. 2. Desenvolver um programa para ler um valor inteiro n, ímpar e maior ou igual a 1 (fazer as verificações necessárias), calcular e apresentar o valor de π (pi), utilizando os n primeiros termos da série abaixo. 24 pi = 4 * ( 1 / 1 - 1 / 3 + 1 / 5 - 1 / 7 + . . . + 1 / n ) 3. Desenvolver um programa para ler um valor inteiro n (1 ≤ n ≤ 100) e os n elementos de um vetor v de double, calcular e apresentar: a) a média aritmética dos elementos múltiplos de 3 (três) b) percentual de elementos negativos c) produtório dos elementos pares e positivos d) índices dos elementos maiores que a média aritmética do vetor. Obs.: • Fazer todas as verificações necessárias ( validade de n, divisão por zero etc. ). • Usar modularidade 4. Desenvolver um programa para ler um valor em binário com no máximo 16 algarismos e apresentá-lo nas bases 10 e 16. 5. Desenvolver um programa para ler um arquivo texto e gerar um arquivo novo, trocando as letras maiúsculas por minúsculas, minúsculas por maiúsculas e os dígitos por sua forma em extenso. Usar redirecionamento. 6. Desenvolver um programa para ler um arquivo texto (usando redirecionamento) e informar o número de vezes que cada letra aparece, distinguindo maiúsculas de minúsculas. 7. Desenvolver um programa para ler um valor inteiro e positivo n, calcular e apresentar o valor de s, utilizando a série abaixo. s = 1 / n1 + 1 / n2 + 1 / n3 + 1 / n4 + . . . + 1 / nn 8. Desenvolver um programa para ler um valor inteiro e positivo n, calcular e apresentar o valor de s, utilizando a série abaixo. s = 1 / n + 2 / ( n – 1 ) + 3 / ( n – 2) + 4 / ( n –3 ) + . . . + n / 1 9. Desenvolver um programa para ler um valor inteiro, positivo e par n, calcular e apresentar o valor de s, utilizando a série abaixo. _ s = √1 / 2 + 2! / 3 + √3 / 4 + 4! / 5 + . . . + n! / ( n + 1 ) 10. Desenvolver um programa para ler uma matriz quadrada (6 x 6) de double, a transforme, dividindo cada linha pelo respectivo elemento da diagonal principal e apresentando o resultado. 11. Desenvolver um programa para ler um valor inteiro n (1 ≤ n ≤ 50), os n elementos de um vetor v de double e um valor double d, determinar e apresentar os índices das ocorrências de d em v. A função que determina as ocorrências deve retornar –1 caso d nâo pertença a v. 12. Desenvolver um programa para ler um valor inteiro n (1 ≤ n ≤ 50), os n elementos de dois vetores v1 e v2 de double, calcular e apresentar o produto escalar destes vetores. O produto escalar deve ser calculado por função específica que trabalhe com vetores de qualquer dimensão. 13. Desenvolver um programa para ler um arquivo texto (usando redirecionamento) e informar: a) número de vogais b) número de consoantes c) número de ocorrência de cada letra maiúscula 25 d) total de letras maiúsculas e minúsculas e) de todos os caracteres do arquivo, o que mais aparece e o que menos aparece. 14. Desenvolver um programa para apresentar os caracteres no código ASCII com valores decimais de 32 a 255. Apresentar também para cada caracter seus códigos decimal e hexadecimal. 12. PONTEIROS ( Pointers ) 12.1. Definição Uma variável ponteiro contem um endereço como valor. Para declarar uma variável como ponteiro, deve-se fornecer o tipo para o qual apontará e o nome da variável. Embora os endereços de variáveis de um tipo, por exemplo int, sejam semelhantes aos de outros tipos como float, char ou double, é importante não misturá-los, pois ao serem feitas operações aritméticas com os ponteiros eles irão variar em função do (tamanho do) tipo. Principais vantagens do uso de ponteiros: • superar as restrições de passagem de argumento por valor, para as funções; • acessar elementos de arrays de modo alternativo ao uso de índices; • permitir a criação de estruturas dinâmicas, como árvores e listas encadeadas bem como facilitar a sua manipulação. Forma de definição: < tipo > *< nome > ; Exemplos: int *p ; ⇒ p é um ponteiro para inteiro float *pf ; ⇒ f é um ponteiro para float int i, *pt ; ⇒ pt é um ponteiro para inteiro pt = &i ; ⇒ pt recebe o endereço de ' i ' i pt 5 1002 1002 2000 *pt ⇒ 5 (conteúdo do endereço dado por pt) void *pvoid ; ⇒ ponteiro para qualquer tipo Dado: float x, y, *p, *q ; São operações possíveis: p = &x ; ⇒ p aponta para x y = *p ; ⇒ y recebe o valor que está no endereço dado por p 26 q = p ; ⇒ q e p apontam para o mesmo endereço Erros comuns: int *p ; *p = 10 ; ⇒ p não apontou para nenhuma variável float val ; int *p ; p = &val ; ⇒ tipos incompatíveis 12.2. Inicialização de ponteiros na declaração int i = 7, *p = & i ; eqüivale a: int i, *p ; i = 7 ; p = &i; 12.3. Passagem de parâmetros por referência (endereço) Para passar um parâmetro por referência, o parâmetro formal deve ser um ponteiro e o parâmetro efetivo deve ser o endereço da variável. Desta forma, na chamada da função, o ponteiro (formal) passa a apontar para a variável, permitindo, assim, que esta variável possa ter seu valor alterado dentro da função. /* ** Exemplo: ler dois valores e trocá-los de ordem */ # include < stdio.h > void swap ( int *, int * ) ; /* protótipo de swap */ void main ( ) { int a, b ; scanf ( " %d %d ", &a, &b ) ; /* leitura dos dados */ swap ( &a, &b ) ; /* chama a função swap */ printf ( " %d %d \n ", a, b ) ; /* imprime resultado */ } void swap ( int *x, int *y ) { int tmp ; tmp = *x ; *x = *y ; *y = tmp ; } Exercício: fazer uma função para somar dois valores inteiros e que tenha o seguinte protótipo: void soma ( int *r, int a, int b ) ; 27 # include < stdio.h > void soma ( int *r, int a, int b ) ; void main ( ) { int x, y, z ; printf ( " \n Entre com dois números: \n " ) ; scanf ( " %d %d ", &x, &y ) ; soma ( &z, x, y ) ; printf (" \n A soma é: %d \n ", z ) ; } void soma ( int *r, int a, int b ) { *r = a + b ; } Obs.: Pode-se usar, por exemplo: float * *q ; Neste caso, q pode armazenar um endereço de um endereço de float. 13. A RELAÇÃO ENTRE PONTEIROS E ARRAYS O nome de um array é um ponteiro (fixo) para a primeira componente deste array. Exemplo: int k, a [100], *p ; . . . p = a; ⇔ p = &a [0]; ⇒ atribui a p o endereço do primeiro elemento do vetor a Para que p aponte para o elemento seguinte, basta incrementar o endereço de 1: p = a + 1; ⇔ p = &a[l]; ⇔ p++; ⇒ p aponta para o próximo elemento . . . k = *p++ ; ⇒ k recebe o valor do elemento apontado por p e, em seguida, p aponta para o próximo elemento Exemplo para zerar um vetor: for ( i = 0; i < 100; i++ ) /* com índice */ a [i] = 0 ; /* cálculo do índice = endereço (base + i * size) */ ou for ( i = 0, p = a; i < 100; i++ ) /* com ponteiro */ p++ = 0; /* melhor, pois utiliza somente operação de soma */ Exercício: Criar a função strlen usando ponteiro. Solução 1: # include < stdio.h > int strlen ( char* str ) ; void main ( ) 28 { static char str[ ] = "Esta string tem 30 caracteres." ; printf ( " \n Tamanho da string: %d caracteres. \n ", strlen ( str ) ) ; } int strlen ( register char *s ) { register int n ; for ( n = 0; *s != '\0'; s++ ) ++n ; return (n) ; } Solução 2: # include < stdio.h > int strlen ( char *str ) ; void main ( ) { static char str [ ] = "Esta string tem 30 caracteres." ; printf ( " \n Tamanho da string: %d caracteres. \n ", strlen (str) ) ; } int strlen ( register char *s ) { register int n = 0 ; while ( *s++ ) ++n ; return (n) ; } Solução 3: # include < stdio.h > int strlen ( char *str ) ; void main ( ) { static char str[ ] = "Esta string tem 30 caracteres." ; printf ( " \n Tamanho da string: %d caracteres. \n ", strlen ( str ) ) ; } int strlen ( register char *s ) { register char *ps ; ps = s ; /* ps aponta para o inicio da string */ while ( *ps ) ps++ ; /* melhor solução: só tem um somatório */ return ( ps - s ) ; } Exercício: Criar a função strcpy, que tem como finalidade copiar o conteúdo de uma string em outra. 29 # include < stdio.h > char *strcpy ( char *d, char *o ) ; void main ( ) { static char orig [ ] = "teste", dest [20] ; strcpy ( dest, orig ) ; printf ( "origem = %s ; destino = %s \n", dest, orig ) ; } char *strcpy (char *dest, char *origem) { char *d = dest ; /* ** o while a seguir faz a copia e incrementa os ponteiros de cada string, até aparecer ** um caracter nulo significando fim de string. Alguns compiladores darão um aviso ** (warning), achando que se deseja fazer um teste (= =) ao invés de atribuição com ** teste, que é o que realmente se pretende fazer. /* while ( *d++ = *origem++ ) ; /* testa *d != '\0'*/ return ( dest ) ; } Exercício: Criar a função strcmp que tem como finalidade comparar o conteúdo de duas strings. A função tem o seguinte formato: int strcmp ( char *s, char *t ) ; Esta função retoma um valor inteiro: • = 0 se as strings forem iguais • < 0 se s < t • > 0 se s > t Solução: # include < stdio.h > int strcmp ( char *s, char *d ) ; void main ( ) { char sl [20], s2 [20] ; printf ( "Entre com a primeira string: " ) ; gets ( sl ) ; printf ( "Entre com a segunda string: " ) ; gets ( s2 ) ; if ( strcmp ( sl, s2 ) ) printf ( " \n Diferentes. \n " ) ; /* <> de zero */ else printf ( "\n Iguais. \n " ) ; } 30 int strcmp ( char *sl, char *s2 ) { while ( *sl = = *s2 ) if ( *sl++ ) s2++ ; else return ( 0 ) ; return ( *sl - *s2 ) ; } Obs.: • Pode-se também inicializar strings usando o seguinte tipo de declaração: char *mens = "Divisao por zero" ; onde a variável mens é declarada como sendo um ponteiro para a string "Divisao por zero". • char str [100] ; str = "casa" ; ⇒ ERRADO char *str ; str = "casa" ; ⇒ CERTO Exercícios: 1. Fazer um programa que utilize uma função que retorne um ponteiro para o maior elemento de um vetor. 2. Fazer uma função que retorne um ponteiro para a primeira ocorrência de um valor em um vetor de doubles ou null caso não encontre. double* rp ( double* pv, int n, double val ) { int i = 0; for ( ; i < n; i++, pv++ ) if ( *pv = = val ) return ( pv ); return ( NULL ); } 14. PONTEIROS E ARRAVS BIDIMENSIONAIS Como para vetores unidimensionais, o nome de um array é um ponteiro fixo. Assim, as seguintes identificações são equivalentes e geram o mesmo código: b [i] [j] * ( * ( b + i ) + j ) * ( b + i * ncolun + j ) O esquema abaixo representa o acesso à vetores bidimensionais. b → . . . . . . b[l] → . . . . . . . . 31 . . . . b[i] ou → . . . . . . b + i ↑ . . *(b + i) . . . . . . b[n] → . . . . . . ↑ b[n][j] ou *(*(b + n) + j) Embora pouco utilizado, existem formas de acessar matrizes de uma forma mais eficiente. Seja, por exemplo, a seguinte declaração de ponteiro para matriz: int mat [10][20], (*plin) [20] ; plin = mat ; A variável plin foi declarada acima como um ponteiro para uma linha de um array com 20 colunas e, em seguida, ele passou a apontar para o primeiro elemento. Forma de armazenamento na memória: . . . . . . . . . 20 elementos 20 elementos 20 elementos Para acessar um elemento da matriz através do ponteiro: (*plin) [j] ou *(*(plin) + j) Para acessar a próxima linha, pode-se usar: plin++ Obs.: • Como em C uma matriz é armazenada por linhas, as variáveis v e mat abaixo, possuem exatamente a mesma forma de armazenamento: static int v [9] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 } ; static int mat [3][3] = { {1, 2, 3} {4, 5, 6} {7, 8, 9} } ; • (*plin) [20] define um ponteiro para linha de uma matriz de 20 colunas, enquanto que *plin [20] define um vetor de 20 elementos do tipo ponteiro. 32 • O nome de um array bidimensional quando usado com um único índice é um ponteiro para o primeiro elemento de uma determinada linha. Ex.: mat [n] ⇒ ponteiro para mat [n][0] Exercício: Calcular a média dos elementos de uma coluna de uma matriz de 3 colunas. # include < stdio.h > double test_avg ( int [ ][3], int, int ) ; void main ( ) { double res ; static int board [ ][3] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 } res = test_avg ( board, 4, 2 ) ; printf ( " %lf \n ", res ) ; } double test_avg ( int tab [ ][3], int maxlin, int col ) { long sum = 0 ; int i, ( *ptr ) [3] = tab ; for ( i = 0; i < maxlin; i++ ) sum += ( *ptr++ ) [col] ; return ( ( double ) sum / maxlin ) ; } Exercício: Calcular a média dos elementos de uma linha de uma matriz de 3 colunas. # include < stdio.h > double test2_avg ( int [ ][3], int ) ; voíd main ( ) { static int board [ ][3] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; double res ; res = test2_avg ( board, 1 ) ; /* calcular para linha 1 */ printf ( " %lf \n ", res ) ; } 33 double test2_avg ( int tab [ ][3], int linha ) { long soma = 0 ; int i ; int p = &tab [linha][0] ; /* endereço do elemento ( linha, 0 ) de tab */ for ( i = 0; i < 3; i++ ) soma += *p++ ; return ( ( double ) soma / 3 ) ; } Obs.: Arrays tridimensionais: seguem as mesmas regras do bidimensional. Exemplo: int v [3][4][5] ; /* array com dimensões 3 x 4 x 5 */ ou int ( *ptrv ) [4][5]; 14.1. Arrays de ponteiros (ragged arrays) São arrays com linhas de tamanho variável. Um array deste tipo é composto por ponteiros para outras variáveis e o seu uso mais comum é para acessar tabelas de strings. Exemplo: definir um vetor de ponteiros para uma tabela de dias da semana. static char *dias [ ] = { "segunda", "terça", "quarta", "quinta", "sexta", "sábado", "domingo" }; Nesta definição, dias é um array de 7 ponteiros para caracteres. O esquema a seguir apresenta a organização interna da tabela dias, declarada anteriormente: dias 0 → s e g u n d a \0 1 → t e r ç a \0 . . . 6 → d o m i n g o \0 Os elementos de 'dias' podem ser acessados de duas maneira: 1. dias [i] Ex.: printf ( " %s \n ", dias [2] ) ; /* será impresso quarta */ 2. dias [i][j] Ex.: printf ( " %c \n ", dias [1][2] ) ; /* será impresso r de terça */ Exercício: Fazer uma função para imprimir 'dias'. # include < stdio.h > void printab ( char*[ ], int ) ; void printab ( char* tab[ ], int n ) { 34 int i ; for ( i = 0; i <= n; i++ ) printf ( " %s \n ", tab [i] ) ; } Obs.: Um elemento de um array de pointers pode ser usado como se fosse um pointer para o primeiro elemento de uma linha de um array bidimensional (tab[i]). 15. DEFINIÇÃO DE NOVOS TIPOS Para definição de novos tipos, utiliza-se a instrução typedef Formato: typedef < tipo existente > < nome do novo tipo >; Exemplos: • para se definir o tipo contador: typedef int contador ; contador i, j ; • para se definir o tipo string: typedef char string [81] ; string text ; • para se definir o tipo boolean: typedef int boolean ; # define FALSE 0 # define TRUE 1 . . . boolean ok = TRUE ; if ( ok ) . . . • para se definir o tipo ptrchar (pointer para caracter): typedef char *ptrchar ; ptrchar pchar ; 16. PONTEIRO PARA PERCORRER ARRAY DE PONTEIROS. Como para qualquer array, o nome de um array de ponteiros é um ponteiro para o primeiro elemento deste array. Quando se passa um array de ponteiros como parâmetro, o que realmente passa é um ponteiro para o primeiro elemento do array. Exemplo: Imprimir tabela 'dias'. typedef char *CHARPTR ; /* define tipo pointer para char */ void print_tab ( CHARPTR *tab_ptr, int n ) { CHARPTR *end_ptrl = tab_ptr + ( n - 1 ); while ( tab_ptr <= end_ptr ) printf ( " %s \n ", *tab_ptr++ ) ; 35 } Obs.: • Uma vantagem de usar o typedefno exemplo acima é a de facilitar a sintaxe, isto é, se não usasse, ao invés de CHARPTR *tab_ptr teria que ser char * * tab_ptr. • Outra alternativa para o corpo da função print_tab seria: { int i ; for ( i = 0; i < n; i++ ) printf ( " %s \n ", *tab_ptr++ ) ; } Esquema dos ponteiros: tab_ptr dias → 0 → s e g u n d a \0 1 → t e r ç a \0 end_ptr . . . → 6 → d o m i n g o \0 16.1. Argumentos para o main As vezes, torna-se necessário passar argumentos para a rotina principal (main) de um programa C. Estes argumentos são fornecidos na chamada para execução do programa. Um exemplo seria: A > listar teste.dat Neste exemplo, o nome do programa é listar e o parârnetro passado é teste.dat. Para receber parâmetros, o main deve possuir o seguinte formato: void main ( int argc, char *argv [ ] ) onde: • argc é o número de parâmetros usados (contando, inclusive, com o nome do programa) • argv é um array de ponteiros para carateres (strings), também chamado de vetor de argumentos. Exemplos: a) Um programa chamado 'echo' que recebe como parâmetro uma mensagem e a exibe no vídeo. Seja a seguinte chamada: echo um dois tres Neste caso tem-se: argc = 4 36 argv → 0 → e c h o \0 1 → u m \0 2 → d o i s \0 3 → t r e s \0 O programa echo poderia ser: /* versão com índice */ # include < stdio.h > void main ( int argc, char *argv [ ] ) { int next ; for ( next = 1; next < argc; next++ ) printf ( " %s%c ", argv [ next ], ( next < argc – 1 ) ? ‘ ’ : ‘ \n ’ ); } /* versão com pointer */ # include < stdio.h > void main ( int argc, char *argv [ ] ) { /* ponteiro para o último parâmetro */ char **last_ptr = argv + argc - 1; /* incrementa antes de testar, pois o primeiro elemento é o nome do programa */ while ( ++argv <= last_ptr ) printf ( " %s%c ", *argv, ( argv < last_ptr ) ? ‘ ’ : ‘ \n ’ ); } 17. PONTEIRO PARA FUNÇÃO Permite a passagem de função como argumento. Formato geral: <tipo> ( *func_ptr ) ( <tipos dos argumentos> ); Obs.: • <tipo> ( *func_ptr ) ( <tipos dos argumentos> ); ⇒ é um ponteiro (func_ptr) para uma função que retorna um valor do tipo <tipo>. • <tipo> *func_ptr ( <tipos dos argumentos> ); ⇒ é uma função que retorna um ponteiro para um valor do tipo <tipo>. Exemplo: int soma ( int, int ); void main ( ) { 37 int ( *ptr_soma ) ( int, int ); int x; ptr_soma = soma; /* pointer para função soma */ x = ( *ptr_soma ) ( 3, 5 ); /* equivale a x = soma ( 3, 5 ); */ } Exercício: Fazer um programa que leia duas strings de dados numéricos ou alfanuméficos e as compare conforme o seu tipo. # include < stdio.h > # include < stdlib.h > # include < string.h > int numcmp ( char *, char * ) ; void check ( char *, char *, int ( *cmp ) ( char *, char * ) ) ; void main ( ) { char sl [80], s2 [80] ; gets (sl) ; /* obtem a primeira string */ gets (s2) ; /* obtem a segunda string */ if ( isalpha (*sl) ) /* a função isalpha retorna <> 0 para letra do alfabeto */ check ( sl, s2, strcmp ) ; /* a função strcmp compara strings */ else check ( sl, s2, numcmp ) ; /* compara como números */ } void check ( char *a, char *b, int ( *cmp ) ( char *, char * ) ) { if ( ! ( *cmp ) ( a, b ) ) printf ( " iguais \n " ) ; else printf ( " diferentes \n " ) ; } int numcmp ( char *a, char *b ) { if ( atoi (a) = = atoi (b) ) /* a função atoi transforma string em inteiro */ return ( 0 ) ; else return ( l ) ; } 18. PRÉ-PROCESSADOR E DIRETIVAS O pré-processador é um programa que age antes do compilador da linguagem C, aumentando sua capacidade e auxiliando a portabilidade. Ele é comandado por diretivas que começam sempre pelo sinal #. As diretivas só valem no arquivo onde estão definidas ou em arquivos incluídos abaixo destas diretivas. As principais diretivas são o # define e o # include. Além destas existem diretivas para compilação condicional (# if, # else, # elif, # endif, # ifdef e # ifndef) e outras utilizadas apenas por determinados compiladores como, por exemplo, para montagem de trechos de programa em linguagem 38 Assembly (# asm e # endasm). Entretanto, estas últimas não são portáveis, pois não são encontradas em todos os compiladores C. 18.1. # define Serve para definir uma constante simbólica. Por exemplo: # define TRUE 1 # define FALSE 0 # define MENS_ERRO " Erro. Divisão por zero. \n " # define MAX_TAM 100 Obs.: Não usar ; ou comentário na linha de um define, pois ele passa a fazer parte dele. As constantes assim definidas podem ser usadas, por exemplo, para atribuir valores a variáveis passadas como parâmetros ou para comparações dentro do programa. Ex.: int erro ; . . . erro = FALSE ; if ( a = = 0 ) erro = TRUE ; . . . if ( erro ) printf ( MENS_ERRO ) ; Outra finalidade do # define é criar macro funções. Por exemplo: # define MIN ( a, b ) ( (a) < (b) ? (a) : (b) ) o comando: a - MIN ( X, 3 ) ; equivale a => a = ( (x) < (3) ? (x) : (3) ) ; # define streq ( x, y ) ( strcmp ( (x), (y) ) = = 0 ) /* iguais */ # define strlt ( x, y ) ( strcmp ( (x), (y) ) < 0 ) /* x < y */ # define strgt ( x, y ) ( strcmp ( (x), (y) ) > 0 ) /* x > y */ Obs.: • Deve-se tomar um certo cuidado ao utilizar macros, pois não há verificação de tipo dos argumentos como nos protótipos das funções. • Macro é mais rápido do que função, porque ela é acrescentada no corpo do programa em cada ponto em que precise ser executada, não havendo, portanto, chamada. Por outro lado, só se deve desenvolver macros para pequenas tarefas, evitando assim que o corpo do programa fique muito grande. • Usar parênteses nos argumentos para evitar erros do tipo: # define quad (x) x * x . . . a = quad ( b + c ) ; /* será calculado b + c*b + c e não (b + c)*(b + c) */ • Para que se possa reutilizar uma constante devemos torná-la antes indefinida, usando a diretiva # undef e a seguir redefiní-la novamente usando outro # define. Exemplo: # define TAM 100 # define COMP 100 char array [ TAM ][ COMP ] ; 39 . . . # undef COMP # define COMP 200 char arrayb [ TAM ][ COMP ] ; 18.2. # include Permite incluir o conteúdo de um arquivo no programa. Exemplo: # include < stdio.h > ⇒ procura stdio.h no diretório de arquivos header # include "stdio.h" ⇒ procura stdio.h no diretório corrente # include "b:auxiliar.h" ⇒ procura auxiliar.h no drive b (não deve ser usado, pois prejudica a portabilidade) 18.3. Compilação Condicional Os comandos para compilação condicional são: # if, # else, # elif, # ifdef, # ifndef e # endif, # if, # else, # elif e # endif São comandos do tipo if then else que indicam para o compilador os trechos do programa que deverão ou não ser compilados. Exemplos de formatos possíveis: # if expressão constante seqüência de comandos # endif # if expressão constante seqüência de comandos 1 # else seqüência de comandos 2 # endif # if expressão constante 1 seqüência de comandos 1 # elif expressão constante 2 seqüência de comandos 2 # else seqüência de comandos 3 # endif Exemplosde uso: a) # define MAX 100 . . . void func ( ) { # if MAX > 99 printf ( " Compilado para array > 99 \n " ) ; # endif . . . } 40 b) # if PC = = 1 # define COMP 1 # else # define COMP 2 # endif Obs.: Neste caso, PC deve ser definido na solicitação de compilação. Ex.: xlc -o teste -d PC = 1 teste.c # ifdef e # ifndef - Usados para testar se algum símbolo foi definido como parâmetro. Exemplos de formatos possíveis: # ifdef símbolo seqüência de comandos # endif # ifdef símbolo seqüência de comandos 1 # else seqüência de comandos 2 # endif # ifndef símbolo seqüência de comandos # endif # ifndef símbolo seqüência de comandos 1 # else seqüência de comandos 2 # endif Exemplos de uso: a) # ifdef DEBUG printf ( " Valor de x : %d \n ", x ) ; # endif b) # ifndef DEBUG printf ( " Sem debug \n " ) ; # endif Obs.: Para evitar redefinição, todo arquivo .h deve iniciar com um # ifndef. Ex.: # ifndef MEU_H # define MEU_H . . . int soma ( int, int ); . . . # define TAM 1000 . . . struct xx 41 . . . # endif 19. TIPOS CONSTRUIDOS 19.1. Tipo enumerado Uma variável de um tipo enumerado pode assumir seu valor somente dentre o conjunto determinado na enumeração. Forma de definição: enum < nome > { <conjunto de valores separados por vírgulas> } ; Exemplo para definir o tipo dia: enum dia { segunda, terça, quarta, quinta, sexta, sábado, domingo } ; enum dia dial, dia2, todos [ 7 ] ; /* as variáveis aqui declaradas só podem assumir */ /* os valores enumerados entre chaves */ dial = segunda ; todos [ 6 ] = domingo ; Obs.: • Deve-se notar que os valores entre chaves não são strings. • Uma outra forma possível de definição/declaração é a seguinte: enum { segunda, terça, quarta, quinta, sexta, sábado, domingo } dial, dia2 ; • Cada valor enumerado na definição é associado a um valor inteiro a partir de 0(zero). Entretanto, pode-se indicar os valores inteiros que se deseja associar: enum frutas { laranja = 7, limão = 6, jaca = 0 } ; enum frutas1 { pera = 2, laranja, abacate, limão = 7 } ; • Os valores dos tipos enumerados não são inteiros. Para usar o valor inteiro associado, deve-se empregar o cast para inteiro: i = ( int ) dial ; 19.2. Estruturas Estruturas (struct) são usadas quando há necessidade de se combinar dados de um mesmo tipo ou de tipos diferentes em um único objeto ou registro. Forma de definição: struct < nome > { < tipol > < campol > ; < tipo2 > < campo2 > ; . . . < tipon > < campon > ; }; Exemplos: struct sl /* Define uma estrutura chamada sl contendo os seguintes campos: */ { int a ; /* inteiro - a */ char nome [ 81 ] ; /* array de caracteres - nome */ 42 char ender [ 100 ] ; /* array de caracteres - endereço */ } Para declarar duas variáveis estruturadas infol e info2, usa-se: struct sl infol, info2 ; Outra forma de definição/declaração poderia ser: struct /* neste caso, não está sendo criado um tipo */ { int x ; int y ; } v1, v2 ; É comum o uso do typedef em conjunto com as estruturas, onde a estrutura passa a ter um nome: typedef struct sl s_s1 ; . . . s_sl infol, info2 ; Para acessar aos membros de uma estrutura, usa-se a seguinte sintaxe: < variável >.< campo > ; Exemplos: infol.a = 10 ; strcpy ( infol.nome, "teste" ) ; struct s2 { int a ; char *nome ; } ; struct s2 info3 ; info3.a = 10 ; info3.nome = " José da Silva " ; /* nome é um ponteiro */ A operação de atribuição e as comparações de igualdade e de desigualdade são possíveis entre estruturas, mas devem ser usadas com certo cuidado, pois podem causar problemas. A partir da definição struct sl vl, v2 ; pode-se executar: v1 = v2; /* tomar cuidado com campo do tipo ponteiro */ . . . if ( vl = = v2 ) /* tomar cuidado com a comparação de lixo */ { . . . } É possível testar se duas estruturas são iguais ou diferentes, mas não se uma é maior ou menor do que a outra. No entanto, os campos (membros) de uma estrutura podem ser comparados com os de outra, normalmente. Apesar da versão ANSI permitir passar estruturas por valor para as funções, isto deve ser evitado, já que torna a chamada da função mais lenta e aumenta a área ocupada no stack. 43 A forma mais indicada para se passar estruturas é utilizando pointers, como será visto mais adiante. 19.3. Inicialização de Estruturas Para alguns compiladores, é necessário que as variáveis locais sejam estáticas. struct s_carta { int carta ; char naipe ; /* Ouros, Copas, Paus e Espadas */ } ; struct s_carta c = { 12, ‘ O ’ /* inicializa como dama de ouros */ } ; 19.4. Uniões Permite o armazenamento de valores de tipos diferentes. Forma de definição: union < nome > { < tipo1 > <campo1 >; < tipo2 > <campo2 >; . . . < tipon > <campon >; }; Exemplo: union mixed { char c ; float f ; int i ; } ; . . . union mixed x ; x . c = ‘ A ’ ; . . . x . f = 10.0 ; . . . x . i = 1 ; A área reservada para uma variável deste tipo é calculada pelo tamanho do maior campo. No exemplo acima, a área reservada teria o tamanho de um float. Deve-se notar que uma variável do tipo union guarda apenas um valor de cada vez, isto é, ao armazenar um dado de um novo tipo, apaga o do tipo anterior. 44 A seguir, é apresentado um exemplo no qual se monta um array de estruturas que guarda elementos de tipos diferentes. /* ** Exemplo: uso combinado de estrutura e união: Cria um array chamado tabela de ** dimensão TAMANHO. Cada elemento do array contem uma estrutura que consiste de ** um ponteiro para caracter (nome), um inteiro (tipo) e um membro do tipo union ** (dados). Cada membro dados do array pode conter um valor do tipo int, float ou char. */ # include < stdio.h > # define INTEGER 0 # define FLOATING 1 # define CHARACTER 2 # define TAMANHO 6 void main ( ) { struct { char *nome ; int tipo ; union { int i ; float f ; char c ; } dados ; } tabela [TAMENHO] ; int j ; /* Inicialização dos elementos da estrutura */ tabela [0] . nome = "Cesar" ; tabela [0] . tipo = CHARACTER ; tabela [0] . dados . c = ‘A’ ; tabela [1] . nome = "do vale" ; tabela [1] . tipo = CHARACTER ; tabela [1] . dados . c = ‘A’ ; tabela [2] . nome = "Ferrari" ; tabela [2] . tipo = CHARACTER ; tabela [2] . dados . c = ‘A’ ; tabela [3] . nome = "Idade" ; tabela [3] . tipo = INTEGER ; tabela [3] . dados . i = 42 ; tabela [4] . nome = "Salário" ; tabela [4] . tipo = FLOATING ; tabela [4] . dados . f = 7200 . 56‘A’ ; 45 tabela [5] . nome = "Imposto" ; tabela [5] . tipo = FLOATING ; tabela [5] . dados . c = 2785 . 89 ; for ( j = 0; j < TAMANHO; j++ ) { printf ( " %s ", tabela [ j ] . nome ) ; switch ( tabela [ j ] . tipo ) { case INTEGER : printf ( " %d \n ", tabela [ j ] . dados . i ) ; break ; case FLOATING : printf ( " %.f \n ", tabela [ j ] . dados . f ) ; break ; case CHARACTER : printf ( " %c \n ", tabela [ j ] . dados . c ) ; break ; default : printf ( " Tipo desconhecido %d - elemento %d . \n ", tabela [ j ] . tipo, j ) ; break ; } } } 20. PONTEIRO PARA ESTRUTURAS Uma estrutura deve ser sempre passada como parâmetro através de um ponteiroe não por valor, mesmo que nenhum dos valores de seus campos necessite ser alterado. Exemplo: # include < stdio . h > struct s_data { int dia, mes, ano ; } typedef struct s_data sdata ; void prtdata ( sdata* ) ; void main ( ) { sdata data ; data . dia = 15 ; data . mes = 3 ; data . ano = 2000 ; 46 prtdata ( &data ) ; /* passa o endereço da estrutura */ } void prtdata ( sdata *data ) { printf ( " %d / %d / %d \n ", data -> dia, data -> mes, data -> ano ) ; /* também poderia usar: (*data ) . dia, (*data ) . mes, (*data ) . ano */ } 21. ARQUIVOS A linguagem C possui alguns arquivos pré-definidos, que são abertos automaticamente quando um programa começa a ser executado. São eles: stdin standard input (default = teclado) stdout standard output (default = vídeo) stderr standard error (default = vídeo) stdprn standard printer (aceito por somente alguns compiladores) Obs.: • Os arquivos stdin, stdout e stderr podem ser redirecionados pelo usuário. • Nos arquivos pré-definidos, a leitura e a impressão são realizadas como se estivesse utilizando um arquivo texto (leitura e gravação sequenciais). O conceito de arquivos em C é um pouco diferente do de outras linguagens. Na maioria das linguagens, o tipo de acesso (direto ou sequencial) está diretamente ligado a forma de abertura do arquivo. Em C, o tipo de acesso (direto ou sequencial) é escolhido pelo usuário, de acordo com as suas necessidades. A linguagem C trabalha com arquivos de uma forma bastante eficiente, utilizando diversas funções. Em primeiro lugar, deve-se incluir o arquivo stdio.h. Deve-se também declarar uma variável do tipo ponteiro para arquivo, utilizando o seguinte formato? FILE *fp ; 21.1. Funções para abertura e fechamento de arquivos 21.1.1. Abertura de arquivos FILE fopen ( char*, char* ) ; Exemplo de chamada: fp = fopen ( nome, modo ) ; Esta chamada abre um arquivo de nome "nome" no modo "modo". "nome" e "modo" são strings de caracteres. Os modos de abertura possíveis são apresentados na tabela a seguir: "nome" no modo "modo". Modo Tipo Read Write Cria Append r t s n n n r+ t s s n n rb b s n n n 47 rb+ b s s n n w t n s s n w+ t s s s n wb b n s s n wb+ b s s s n a t n s s s a+ t s s s s ab b n s s s ab+ b s s s s Onde: • r ⇒ read • w ⇒ write • a ⇒ append • t ⇒ texto • b ⇒ binário • s ⇒ sim • n ⇒ não Obs.: • r+ ⇒ lê e escreve. • w+ ⇒ escreve e lê. • rb+ ⇒ atualiza registro. • w ⇒ sempre cria arquivo. • a ⇒ cria se não existir. Se existir, grava no final. • r ⇒ nunca cria. Em caso de sucesso na abertura, a função fopen retorna um ponteiro para o arquivo, que será usado como nome lógico deste arquivo dentro do programa (fp do exemplo de chamada). Em caso de erro, retorna NULL. É recomendável que o usuário sempre verifique se o arquivo foi aberto ou não. Exemplo: fp = fopen ( " teste.dat ", " r " ) ; if ( fp = = NULL ) { printf ( " \n Erro: abertura de arquivo para leitura " ) ; exit (1) ; } O nome do arquivo pode indicar um nome simples ou um nome com path (caminho). Exemplos: " teste.dat " " C:\programas\testes\teste.dat ", 21.1.2. Fechamento de arquivos Protótipo: int fclose ( fp ) ; Chamada: fclose ( fp ) ; 48 Em caso de sicesso, a função retorna 0 (zero) e em caso de erro, um valor diferente de 0 (zero). Exemplo: (caso usual) . . . if ( fp = fopen ( " teste.dat ", " r " ) = = NULL ) { printf ( " \n Erro de abertura para leitura " ) ; exit (1) ; } . . . fclose ( fp ) ; 21.2. Principais funções para leitura e gravação sequenciais 21.2.1. Leitura fgets Protótipo: char *fgets ( char*, int, FILE* ) ; Exemplo de chamada: pchar = fgets ( buffer, num, fp ) ; Lê caracteres do arquivo dado por fp. A leitura termina quando forem lidos (num - 1) caracteres ou quando encontrar o caracter NL (New Line = \n) ou EOF (End Of File = \0). Os caracteres lidos são colocados em buffer. Se a leitura foi correta, a função retorna um ponteiro para buffer e em caso de erro, retorna NULL. Obs.: • O buffer deve ser grande o suficiente para armazenar os caracteres \0 e \n. • A função fgets é muito usada para arquivos do tipo texto. Exemplo: . . . char *pchar, buffer [100] ; FILE *fp ; . . . pchar = fgets ( buffer, 98, fp ) ; . . . fgetc Protótipo: int fgetc ( FILE* ) ; Exemplo de chamada: v_int = fgetc ( fp ) ; Lê um caracter do arquivo dado por fp e retorna o seu código ASCII. Se a leitura chegar ao final do arquivo ou houver erro de leitura, retorna EOF. O uso desta função requer um certo cuidado, pois EOF pode ser um caracter válido em arquivos abertos no modo binário. Obs.: Esta é uma função pouco usada. fscanf Protótipo: int fscanf ( FILE*, char*, . . . ) ; Exemplo de chamada: fscanf ( fp, fmt, args ) ; 49 Funciona de forma semelhante à função scanf. Lê as variáveis em args com o formato fmt do arquivo dado por fp. Em caso de sucesso, retorna o número de variáveis que foram lidas e caso chegue ao fim do arquivo, retorna EOF. Obs.: • Esta é uma função muito usada, principalmente para leitura de valores numéricos. • Depois da fgets pode usar a fscanf, mas depois da fscanf, pode dar problema usar a fgets. 21.2.2. Gravação fputs Protótipo: int fputs ( char*, FILE* ) ; Exemplo de chamada: fputs ( str, fp ) ; Grava a string str no arquivo dado por fp. A função retorna 0 (zero) se gravou corretamente ou outro valor em caso de erro. Obs.: Grava a string e para, não registrando mudança de linha. fputc Protótipo: int fputc ( int, FILE* ) ; Exemplo de chamada: fputc ( carac, fp ) ; Grava o caracter carac no arquivo dado por fp. Em caso de erro, retorna EOF. Obs.: Deve-se atentar para o fato de que se o arquivo for aberto no modo binário, EOF pode ser um caracter válido. fprintf Protótipo: int fprintf ( FILE*, char*, . . . ) ; Exemplo de chamada: fprintf ( fp, fmt, args ) ; Funciona de forma semelhante à função pritf. Lê as variáveis de args com o formato fmt no arquivo dado por fp. Em caso de sucesso, retorna o número de caracteres que foram gravados e em caso de erro, retorna um número negativo. Obs.: • Esta é a função mais usada. Faz tudo que a fputs faz. • As funções fscanf e fprintf realizam, respectivamente, leitura e gravação em strings. Exercício: Fazer um programa que copie um arquivo em disco para o outro. Os nomes dos arquivos são fornecidos com parâmetros na linha de comando, sendo o primeiro o nome do arquivo origem e o segundo o nome do arquivo destino. # include < stdio.h > # include < stdlib.h > # include < string.h > void main ( int argc, char *argv [ ] ) { 50 char buffer [512] ; char arqorg [150], arqdst [150] ; FILE *nome1, *nome2 ; if ( argc < 3 ) { printf ( " \n Erro: Número de parâmetros incorreto " ) ; exit (1) ; } strcpy ( arqorg, argv [1] ) ; strcpy ( arqdst, argv [2] ) ; nome1 = fopen ( arqorg, " rb " ) ; nome2 = fopen ( arqdst, " wb " ) ; if ( nome1 = = NULL ⎥⎟ nome2 = = NULL ) { printf ( " Erro na abertura dos arquivos. \n " ) ; exit (1) ; } while ( fgets ( buffer, 512, nome1 ) ) fprintf ( nome2, buffer ) ; fclose ( nome1 ) ; fclose ( nome2 ) ; } 21.3. Principais funções para leitura e gavação direta ( binàrio ) Normalmente, para acesso direto abre-se o arquivo comobinário, para que a correspondência de caracteres seja de 1 para 1. 21.3.1. Funções para posicionamento rewid Protótipo: void rewind ( FILE * ) ; Exemplo de chamada: rewid ( fp ) ; Coloca o ponteiro interno de I/O no início do arquivo dado por fp. fseek Protótipo: int fseek ( FILE*, long, int ) ; Exemplo de chamada: fseek ( fp, offset, origem ) ; Move o ponteiro interno de I/O do arquivo dado por fp conforme offset e origem. O parâmetro offset (do tipo long int) indica o número de bytes, a partir da origem, que o ponteiro deve se deslocar. Os valores possíveis para origem são: 0 ou SEEK_SET - início do arquivo 1 ou SEEK_CUR - posição atual do ponteiro 2 ou SEEK_END - final do arquivo A função fseek retorna 0 (zero) para sucesso e um valor diferente de 0 (zero) para erro. 51 Por exemplo, a instrução fseek (fp, 0l, SEEK_SET) é equivalente a rewind (fp). Obs.: Esta é uma função muito usada. ftell Protótipo: long ftell ( FILE *stream ) ; Exemplo de chamada: pos = ftell ( fp ) ; Retorna a posição atual (em bytes) do ponteiro do arquivo, em relação a origem. 21.3.2. Leitura fread Protótipo: int fread ( void*, int, int, FILE* ) ; Exemplo de chamada: fread ( buf, tam, nblocos, fp ) ; Le para buf, nblocos de tam bytes do arquivo dado por fp. A função fread retorna o número de blocos lidos. 21.3.3. Gravação fwrite Protótipo: int fwrite ( void*, int, int, FILE* ) ; Exemplo de chamada: int fwrite ( buf, tam, nblocos, fp ) ; Funciona como a fread, só que para gravação. A função retorna o número de blocos que foram gravados. Exemplo: Gravar e ler a variável double x em um arquivo. . . . double x ; . . . fwrite ( &x, sizeof ( double ), 1, fp ) ; . . . fread ( &x, sizeof ( double ), 1, fp ) ; Obs.: Na pesquisa, principalmente para gravação, após abrir e gravar, é recomendável fechar o arquivo, obrigando o descarregamento do buffer. A função fflush (FILE*); é outra alternativa para causar este mesmo efeito. Exercício: Fazer um oprograma que inicialize um vetor de 100 elementos com os números de 0 a 99, o grave em um arquivo e, em seguida, leia o conteúdo do arquivo para outro vetor. # include < stdio.h > # include < stdlib.h > 52 viod main ( ) { FILE *fp ; int i ; float v1[100], v2[100], *pv ; for ( pv = v1, i = 0; i < 100; i++ ) *pv++ = ( float ) i ; /* inicializa o vetor */ /* abre o arquivo */ if ( ( fp = fopen ( " teste.dat ", " wb " ) ) = = NULL ) { printf ( " \n Erro de abertura: gravação " ) ; exit (1) ; } fwrite ( v1, sizeof ( float ), 100, fp ) ; /* grava vetor */ fclose ( fp ) ; /* fecha o arquivo */ /* reabre o arquivo */ if ( ( fp = fopen ( " teste.dat ", " rb " ) ) = = NULL ) { printf ( " \n Erro de abertura: leitura " ) ; exit (2) ; } fread ( v2, sizeof ( float ), 100, fp ) ; /* le para novo vet */ fclose ( fp ) ; /* fecha o arquivo */ for ( pv = v2, i = 0; i < 100; i ++ ) printf ( " %.2f \n ", pv++ ) ; /* imprime vetor lido */ } Exercício: Fazer um programa que grave em um atrquivo três vetores de 100 elementos cada, sendo o primeiro formado pelos números de 0 a 99, o segundo os 100 primeiros numeros pares e o terceiro os os 100 elementos múltiplos de 3. A seguir, fechar o arquivo e reabrí-lo lendo e imprimindo cada um dos vetores. # include < stio.h > # include < stdlib.h > # define TAM 100 void main ( ) { FILE *fp ; int i ; float v1( TAM ), *pv ; /* Abre o arquivo testando se houver erro. */ if ( ( fp = fopen ( " teste.vet ", " wb " ) ) = = NULL ) { printf ( " \n Erro ao abrir arquivo inicial. \n " ) ; exit (1) ; } 53 for ( pv = v1, i = 0; i < TAM; i++ ) pv++ = ( float ) i ; /* gera números de 0 a 99 */ /* Grava apenas um bloco com tamanho v1, o que equivale a gravar ( sizeof ( float ) * TAM ) bytes. */ if ( fwrite ( v1, sizeof ( v1 ), 1, fp ) != 1 ) { printf ( " \n Erro ao gravar vetor 1. \n " ) ; exit (2) ; } for ( pv = v1, i = 0; i < TAM; i ++ ) *pv++ = ( float ) i * 2 ; /* vet: 0, 2, 4, . . . , 198 */ /* Grava TAM blocos com o tamanho de float, o que equivale a gravar ( sizeof ( float ) * TAM ) bytes e testa se gravou tudo. */ if ( fwrite ( v1, sizeof ( float ), TAM, fp ) != TAM ) { printf ( " \n Erro ao gravar vetor 2. \n " ) ; exit (3) ; } for ( pv = v1, i = 0; i < TAM; i++ ) *pv++ = ( float ) i * 3 ; /* vet: 0, 3, 6, . . . , 297 */ /* Grava TAM blocos com o tamanho de float, o que equivale a gravar ( sizeof ( float ) * TAM ) bytes e testa se gravou tudo. */ if ( fwrite ( v1, sizeof ( float ), TAM, fp ) != TAM ) { printf ( " \n Erro ao gravar vetor 3. \n " ) ; exit (4) ; } fclose ( fp ) ; /* fecha o arquivo */ /* Reabre arquivo testando se houve erro. */ if ( ( fp = fopen ( " teste.vet ", " rb " ) ) = = NULL ) { printf ( " \n Erro ao reabrir o arquivo. \n " ) ; exit (5) ; } fread ( vl, sizeof ( vl ), 1, fp ) ; /* lê primeiro vetor */ for ( pv = vl, i = 0; i < TAM; i++ ) printf ( " %f ", *pv++ ) ; /* imprime */ printf ( " \n \n " ) ; /* pula 2 linhas */ rewind ( fp ) ; /* volta ao inicio do arquivo */ /* A partir do inicio e com deslocamento de vl, aponta para o segundo vetor */ fseek ( fp, ( long ) sizeof ( vl ), 0 ) ; fread ( vl, sizeof ( float ), TAM, fp ) ; /* lê segundo vetor */ 54 for ( pv = vl, i = 0; i < TAM; i++ ) printf ( " %f ", *pv++ ) ; /* imprime */ printf ( " \n \n " ) ; /* pula 2 linhas */ rewind ( fp ) ; /* volta inicio arquivo */ /* A partir do início, com deslocamento de (2 * vl), aponta para o terceiro vetor */ fseek ( fp, ( long ) sizeof ( vl ) * 2, 0 ) ; fread ( vl, sizeof ( vl ), 1, fp ) ; /* lê terceiro vetor */ for ( pv = vl, i = 0; i < TAM; i++ ) printf ( " %f ", *pv++ ) ; /* imprime */ printf (" \n \n " ) ; /* pula duas linhas */ fclose ( fp ) ; /* fecha arquivo */ } Exemplo do uso de estruturas em arquivos: struct s_prop { char nome [20] ; double mod ; double coef ; } ; typedef struct s_prop sprop ; sprop prop ; sprop variasprops [100] ; . . . /* grava uma propriedade */ fwrite ( &prop, sizeof ( sprop ), 1, fp ) ; . . . /* grava vetor com várias propriedades */ fwrite ( variasprops, sizeof ( sprop ), 100, fp ) ; . . . 22. ALOCAÇÃO DINÂMICA DE MEMÓRIA A linguagem C possui uma série de funções que permitem alocar e liberar áreas de memória, fazendo com que se possa usar a memória gasta por