Vamos falar agora propriamente dita da manipulação          dos arquivos em C, leitura e escrita. 
               Escrevendo em um arquivo
               Para escrevermos em um arquivo em C, utilizamos as funções          fputc e putc. Eles escrevem na stream, um caracter por vez. O protótipo          é idêntico:
       int fputc(int c, FILE *stream);
       As duas funções fazem exatamente a mesma          coisa, só que uma, a putc(), deve ser implementada em termos de          macro, para manter compatibilidade. As versões antigas utilizavam          o putc(), e para manter tudo funcionando, em novas implementações          se fez isso.
               Lendo um caracter de arquivo
               Igualmente, definido pelo padrão C ansi, temos          as funções getc e fgetc. O protótipo é: 
       int fgetc(FILE *stream);
       Igualmente a dupla fputc e putc, a putc é implementada          como macro. Elas servem para ler caracteres da stream passada, que será          o retorno da função. Caso chegue ao final do arquivo retornará          EOF (End of File) ou erro. 
       Também vale relembrar, que a função          getchar() é equivalente a getc(stdin), ou seja, lê caracteres          da entrada padrão. Já putchar(), é equivalente a          putc(caracter, stdout), ou seja, escreve no dispositivo padrão          de saída.
       As funções citadas fazem parte da stdio.h,          e podem ser utilizadas de forma complementar, ou até com outras          funções de streams. Essa é uma das características          que tornam o C uma das linguagens mais versáteis e poderosas do          mundo .
                              Bem, agora falaremos de leitura de arquivos de forma          mais geral, através de strings. 
               Escrevendo em um arquivo linha a linha
               A função fputs() é igual a função          putc, só que em vez de trabalhar caracter a caracter, ela trabalha          linha a linha, geralmente uma situação de conforto para          o programador. O protótipo dela segue:
       int fputs(const char *s, FILE *stream);
       Repare que passamos um ponteiro para char, onde passaremos          a string (a linha) e especificamos qual stream estamos utilizando. Note          que é idêntica a função puts, só que          deixa especificar a stream, ao contrário da puts que utiliza stdin.          protótipo: int puts(const char *s);
               Lendo arquivo linha a linha
               Você também, analogamente, pode ler arquivos          linha a linha. Repare o protótipo da função: 
       char *fgets(char *s, int size, FILE *stream);
       Ela retorna um ponteiro para string recebida, ela para          até achar o fim da linha ou tiver alcançado o tamanho de          size-1. Stream especifica de onde está sendo lido, e o retorno          vai sempre para o ponteiro que está recebendo. Caso tenha sido          encontrado o final de linha ou EOF, o \0 é armazenado depois do          último caracter lido.
                      Voltando no arquivo..
               Bem, sabemos que existe um descritor de arquivo, a tal          estrutura do tipo FILE, definida no cabeçalho stdio.h. Bem, nesta          descrição, quando é montada a estrutura em memória,          existe um de seus campos que indica a posição onde o ponteiro          está posicionado para leitura/escrita. É assim que fazemos          para ler caracteres e escreve-los. Existe a função chamada          fseek, que existe para reposicionar o indicador de posição          do descritor de string. Isso mesmo, ela só muda para onde o ponteiro          está direcionado. Você pode correr ele para o início          do arquivo, final, ou uma posição definida por sua pessoa          mesmo. 
       O protótipo da função é:          int fseek( FILE *stream, long offset, int whence);
       O que isso quer dizer? Bem, você passa qual stream          está trabalhando, o descritor logicamente, passa quantos bytes          deseja percorrer a partir de uma posição predeterminada.          Ou seja, o campo whence, você passa onde que é a posição          base para início de calculo, por exemplo, existem a possibilidade          de passar as macros: SEEK_SET, SEEK_CUR e SEEK_END, que você posiciona          no início do arquivo, posição corrente ou no final          do arquivo. Aí você passa quantos bytes deseja trabalhar          a partir dessa posição. O retorno da função          é 0 caso tenha obtido sucesso e diferente caso não o tenha.
       Existem as funções ftell() (protótipo:          long ftell( FILE *stream);), que retorna justamente onde está posicionado          o indicador de posição da stream e a função          rewind() (protótipo: void rewind( FILE *stream);) que volta ao          início do arquivo, é como um (void)fseek(stream, 0L, SEEK_SET).
Mantendo tudo sob controle
               Vamos relembrar: um arquivo em C é uma stream,          um "fluxo" de dados. Como tudo em computação, uma stream          possui um tamanho finito. Se efetuarmos diversas leituras nela, indo da          primeira posição em frente, alcançaremos um ponto          chamado EOF, que é o fim do arquivo (em inglês, End Of File).          Deste ponto, só podemos retroceder; nem pense em ler mais alguma          coisa do arquivo! E como sabemos que chegamos ao fim do arquivo? Simplesmente          perguntando. Na biblioteca padrão temos a função          feof que nos retorna se o arquivo já terminou. O protótipo          dela é:
       int feof(FILE *stream)
       Ela retorna 0 se o arquivo não chegou ao fim ou          1 caso contrário (lembre que em C o valor inteiro que corresponde          a uma afirmação falsa é 0 e uma afirmação          verdadeira corresponde a um inteiro diferente de zero).
       Por exemplo, para determinar se chegamos ao final da          stream apontada pela variável Cadastro (que já deverá          ter sido aberta com fopen), podemos fazer o seguinte teste:
       if (feof(Cadastro))
        printf ("Fim do arquivo");
       Aqueles que são bons observadores, já devem          ter notado que os exemplos dados na última aula já fazem          uso desta função (propositalmente). Sugiro a todos que dêem          mais uma olhada neles.
       Uma outra função que é útil          para verificar como está uma stream é a ferror. Novamente,          como tudo em computação, um acesso a uma stream pode ser          efetuado com sucesso, ou pode falhar (pessimistas acham que a probabilidade          de falhar é maior do que a de acertar). Para ter certeza de que          eu não tenha resultados imprevistos na hora em que um acesso der          errado, eu testo o indicador de erro da stream logo após tê-la          acessado. Se for 0, não há erro; se for diferente de zero,          "Houston, temos um problema". O protótipo de ferror é:
       int ferror(FILE *stream)
       Vamos dar uma olhada em um pedacinho de código          só para fixarmos esta nova função. Neste exemplo,          a stream apontada por Lista já deverá estar aberta, e Item          é uma string:
       fgets(Item, 40, Lista);
        if (ferror(Lista)) {
        printf ("Apertem os cintos: ocorreu um erro no último acesso!");
        exit(1);
        }
       Note bem: o indicador de erro poderá mudar se          for feito uma nova leitura ou escrita antes de testá-lo. Se quiser          testar, teste logo após executar um acesso.
               Escreveu, não leu...
               Já notou que as funções fgets e          fputs se parecem muito com as suas primas gets e puts, sendo que estas          lêem do teclado e escrevem na tela e aquelas fazem o mesmo, só          que relativamente a streams? Seria bom se nós tivéssemos          algo do tipo printf e scanf para arquivos; e nós temos! Usando          fprintf e fscanf nós fazemos o mesmo que já fazíamos          na tela, mas agora faremos em streams. Veja como elas estão definidas          nas bibliotecas padrão:
       fprintf (FILE *stream, char *formato, ...)
       fscanf (FILE *stream, char *formato, ...)
       Na prática, podemos usar estas duas funções          que atuam em streams do mesmo modo como usamos suas primas, já          velhas conhecidas nossas, mas acrescentando antes o ponteiro para a stream.          Assim, se eu tinha um programa que lia do teclado dados digitados pelo          usuário, posso fazer uma mudança para ler de um arquivo.          De modo similar, isso também vale para a tela. Por exemplo, sejam          dois arquivos apontados pelas variáveis Saída (aberto para          escrita) e Dados (aberto para leitura):
       se eu tinha: scanf("%d",&numero);
        posso fazer: fscanf(Dados,"%d",&numero);
       se eu tinha: printf("Tenho %d unidades do produto: %s\n",quant,prod);
        posso fazer: fscanf(Saida,"Tenho %d unidades do produto: %s\n",quant,prod);
       Experimente usar isto nos programas que você já          fez, só para praticar: ao invés de usar de scanf e printf,          leia dados com fscanf de um arquivo e escreva-os em um outro arquivo com          fprintf. O arquivo gerado poderá ser aberto inclusive em editores          de texto, e você poderá ver o resultado. Mas ATENÇÃO:          fprintf e fscanf devem ser aplicados a streams texto.
       Até agora, todas as funções que          lêem e escrevem em um arquivo se referem a streams texto. É          uma boa hora para se questionar a utilidade das tais streams binárias.
       Podemos querer ler e/ou escrever uma stream tendo controle          total dos bytes dela, para, por exemplo, copiar fielmente um arquivo,          sem alterar nada nele. Assim, abriremos o arquivo usando o já mencionado          qualificador b. E para acessar os dados, teremos duas novas funções,          fread e fwrite, definidas da seguinte forma:
       size_t fread(void *Buffer, size_t TamItem, size_t Cont, FILE *Fp)
        size_t fwrite(void *Buffer, size_t TamItem, size_t Cont, FILE *Fp)
       O tipo size_t é definido na biblioteca STDIO.H          e, para simplificar, podemos dizer que eqüivale a um inteiro sem          sinal (unsigned int). Lembre-se de que void* é um ponteiro qualquer,          ou seja, um ponteiro sem tipo. Assim, eu posso usar um int*, char*, ...
       A função fread opera do seguinte modo:          lê da stream apontada pela variável Fp tantos itens quantos          forem determinados pela variável Cont (cada item tem o tamanho          em bytes descrito em TamItem) e coloca os bytes lidos na região          de memória apontada por Buffer. A função fwrite funciona          de modo similar, porém gravando o que está na região          de memória apontada por Buffer na stream Fp.
       As duas funções retornam o número          de bytes efetivamente lidos ou escritos do arquivo, respectivamente.
       Para determinar o tamanho em bytes de um determinado          tipo de dado que queremos ler ou gravar, é possível usar          o operador sizeof. Como exemplo, podemos querer gravar na stream binária          apontada pela variável Dados o valor da variável inteira          X:
       fwrite(&X,sizeof(int),1,Dados);
       O tamanho de um inteiro é determinado por sizeof(int).          Note que o número de bytes que foram efetivamente gravados, retornado          pela função, pode ser desprezado (e geralmente o é).
       Seja a struct Pessoa definida por (lembra-se das aulas          sobre struct?):
       struct Pessoa {
        char nome[40];
        int idade;
        };
       Definamos a variável Aluno do tipo struct Pessoa.          Podemos ler um registro de um aluno de um arquivo com o comando:
       fread(&Aluno,sizeof(struct Pessoa),1,Dados);