== Arquivos :cap: cap5 :online: {gitrepo}/blob/{edition}/livro/capitulos/code/{cap} :local: code/{cap} .Objetivos do capítulo ____ Ao final deste capítulo você deverá ser capaz de: * Entender o conceito de arquivos e seus tipos; * Conhecer as semelhanças e diferenças entre Arquivos e Fluxos; * Escrever programas em C que leiam e escrevam corretamente na entrada padrão, na saída padrão e na saída de erros; * Verificar se houve erro durante as operações de leitura e escrita ou se chegou ao final do arquivo; * Utilizar as funções `fgetc` ou `getc` para ler carácteres e `fputc` ou `putchar` para escrever carácteres em arquivos; * Utilizar as funções `fgets` e `fputs` para ler e escrever linhas em arquivos somente texto; * Utilizar as funções `fread` e `fwrite` para ler e escrever em arquivos binários; * Compreender o que é o _indicador de posição_ de arquivos e identificar as funções que o manipula; ____ Até agora foi visto como é possível gravar e acessar informações/dados na memória primária/principal do computador (também chamada memória RAM). Para isso, foi visto que o uso de ponteiros é fundamental. O problema dessa abordagem é que as informações/dados de um programa em C gravadas na memória RAM são perdidas após o término da execução desse programa. Ou seja, não se pode mais acessá-las ou recuperá-las. Como fazer para, após a execução de um programa em C, ter acesso a dados/informações manipuladas nesse programa? Uma solução é gravar esses dados/informações em um arquivo na memória secundária do computador (também chamado de Disco Rígido – Hard Disk drive). Nesse caso, após a execução do programa em C, esses dados/informações estarão disponíveis no arquivo gravado no disco rígido da máquina. Nesse capítulo serão abordados conceitos básicos sobre arquivos e formas de manipulação de dados no disco rígido na linguagem C, como por exemplo, estratégias para salvar e recuperar dados/informações em arquivos. [[sec_tipos_de_arquivos]] === Os tipos de arquivos (((Arquivo, somente texto))) Para compreender a utilização de arquivos em C precisamos conhecer os diferentes tipos de arquivos que existem: Arquivos somente-texto:: Estes arquivos possuem somente carácteres de texto, exemplos deles são arquivos TXT, XML, HTML entre outros. Outra característica importante é que seu conteúdo costuma ser separado por _quebras de linha_. Além disso, os sistemas operacionais codificam a quebra de linha de forma diferente. Alguns aplicativos aceitam entradas no formato somente-texto, causando erro de execução caso encontre carácteres binários. (((Arquivo, binário))) Arquivos binários:: Este arquivos possuem bytes que não representam carácteres textuais, exemplos deles arquivos MP3, JPEG, ZIP etc. O sistema operacional Windows requer que as funções de escrita e leitura dos arquivos se comporte de forma diferente, de acordo com o tipo de arquivo. (((Arquivo, especial))) Arquivos especiais:: São arquivos que não armazenam conteúdo, veremos este tipo no <>. === Arquivos e fluxos Nesta seção vamos apresentar as características de fluxo de dados e arquivos e suas similaridades importantes para linguagem C. ==== Fluxo de dados (((Fluxo de dados))) Para facilitar o aprendizado na utilização de arquivos em C precisamos conhecer o conceito de fluxo de dados (_data stream_). O áudio que escutamos numa rádio é exemplo de um _fluxo de dados_. As estações de rádio transmitem o fluxo e seu aparelho toca todos os trechos que recebe. Uma vez sintonizado no fluxo, não há como pausar, retroceder ou avançar nele, só é possível tocar o que está sendo recebido. O sistema de televisão também transmite as imagens através de fluxo, no entanto, existes aparelhos que possibilitam pausar a programação. Neste caso o aparelho passa a gravar todo o conteúdo que continua sendo recebido pelo fluxo em um Buffer, geralmente um disco interno. Quando o usuário remove o pause, a programação é exibida a partir do conteúdo salvo. Também é possível avançar no vídeo, mas somente até o final do Buffer, quando atinge o ponto de recepção do fluxo novamente. IMPORTANT: O fluxo diferencia de arquivo pois, no fluxo, não podemos avançar ou retroceder na leitura. A leitura do fluxo costuma ser sequencial, realizando o processamento dos dados logo após a leitura. Como os arquivos possuem início e fim é possível determinar o seu tamanho. Já os fluxos possuem início, mas nem todos possuem fim. ==== Arquivos (((Arquivo))) O principal propósito dos arquivos é salvar um conteúdo para ser acessado futuramente. Para isso os arquivos possuem nomes e são organizados em diretórios (ou pastas), para facilitar sua identificação. A organização dos nomes dos arquivos não é a mesma em todos os sistemas operacionais. No Windows, por exemplo, existe um diretório raiz por disco (`C:\`, `D:\` etc.) e os diretórios são separados pela carácter ``\'' (_Contra barra_). Já no Unix, existe um único diretório raiz `/` onde todos os demais diretórios são _descendentes_ dele, o carácter separador de diretório é o ``/'' (_Barra_), diferente do Windows. Os arquivos somente texto também são diferentes, enquanto no Windows o final de linha é codificado com dois carácteres, o Linux utiliza apenas um. ==== Arquivos em C IMPORTANT: A principal noção que precisamos ter em mente quando trabalhamos com arquivos em C é que *fluxos e arquivos são manipulados através de uma interface única*. O tipo de dado para manipular Arquivos e Streams é o `FILE`, definido no cabeçalho <>. Na estrutura `FILE` são registradas todas as informações necessárias para sua manipulação: * Um indicador da posição no arquivo. * Um ponteiro para seu Buffer (caso exista). * Um indicador de erro, que registra caso houve algum erro de leitura/escrita. * Um indicativo de _final de arquivo_, onde é registrado se o final do arquivo foi atingido. NOTE: Perceba que a estrutura `FILE` não possui registro do nome do arquivo que está sendo manipulado. === Cabeçalho `stdio.h` Todas as funções que serão apresentadas neste capítulo estão definidas no cabeçalho `stdio.h`, requerendo a inclusão deste arquivo para sua utilização: [source,c] ---- #include ---- === Abrindo e Fechando arquivos em C Toda vez que desejamos ler ou escrever em um arquivo precisamos *abrir o arquivo* indicando esta intenção. ==== Abrindo um arquivo com `fopen` (((Arquivo, abrindo))) (((fopen))) A abertura de arquivos em C é realizada através da função <>, que retorna um ponteiro `*FILE` caso a operação foi realizada com sucesso ou `NULL` caso houve erro na abertura: [source,c] ---- FILE* arquivo = fopen(nomeDoArquivo, modo); ---- A intenção da abertura do arquivo deve ser especificada no parâmetro `modo`: r:: abre arquivo de texto para leitura w:: abre arquivo de texto para escrita wx:: cria arquivo de texto para escrita a:: adiciona ao final; o indicador de posição de arquivo é posicionado no final do arquivo rb:: abre arquivo binário para leitura wb:: abre arquivo binário para escrita ab:: abre arquivo binário para escrita, no final do arquivo Os códigos a seguir demonstram a abertura de arquivo para _leitura no modo texto_ (<>) e outro para _escrita no modo binário_ (<>). [[ex_abrindo_arquivo_de_texto]] .Abrindo um arquivo de texto para leitura ==== :srcfile: abrindo_arquivo_de_texto.c :saida: abrindo_arquivo_de_texto.out .Código fonte {online}/{srcfile}[{local}/{srcfile}] [source, c] ---- include::{local}/{srcfile}[] ---- .Saída da execução .... include::{local}/{saida}[] .... ==== [[ex_abrindo_arquivo_binario_para_escrita]] .Abrindo um arquivo binário para escrita ==== :srcfile: abrindo_arquivo_binario_para_escrita.c :saida: abrindo_arquivo_binario_para_escrita.out .Código fonte {online}/{srcfile}[{local}/{srcfile}] [source, c] ---- include::{local}/{srcfile}[] ---- .Saída da execução .... include::{local}/{saida}[] .... ==== [TIP] ==== A especificação dos nomes dos arquivos requer que recordemos como os strings são escritos e representados na linguagem C: ["graphviz", scaledwidth="70%"] ---- include::../images/cap5/string.dot[] ---- ==== ==== Entrada e saídas padrões (((stdin))) (((stdout))) (((stderr))) O cabeçalho <> também define os seguintes arquivos como entradas e saídas padrões, que dispensam a operação de abertura: `FILE* stdin`:: Arquivo que representa a entrada padrão. `FILE* stdout`:: Arquivo que representa a saída padrão. `FILE* stderr`:: Arquivo que representa a saída de erro padrão. NOTE: Perceba que nos exemplos <> e <> a impressão das mensagens de erro são escritas da saída de erro `stderr`. ==== Fechando um arquivo (((Arquivo, fechando))) (((fclose))) Independente do modo utilizado para abrir o arquivo, todo arquivo aberto deve ser fechado após sua utilização. Para fechar um arquivo utilizamos a função <>, passando como parâmetro o ponteiro `FILE*` retornado na abertura do arquivo: [[ex_fechando_arquivo]] .Fechando um arquivo ==== :srcfile: fechando_arquivo.c :saida: fechando_arquivo.out .Código fonte {online}/{srcfile}[{local}/{srcfile}] [source, c] ---- include::{local}/{srcfile}[] ---- <1> A função para fechar o arquivo só deve ser invocada caso o arquivo foi aberto com sucesso. .Saída da execução .... include::{local}/{saida}[] .... ==== === Indicadores de erro e final de arquivo (((Arquivo, indicadores))) Na próximas seções serão apresentadas várias funções para ler e escrever em arquivos. Todas todas as funções que serão apresentadas registram eventos de erro e final de arquivo em indicadores na estrutura `FILE` que poderão ser consultado através das funções que serão apresentadas nesta seção. ==== Erro na leitura/escrita (((Arquivo, indicador de erro))) Toda vez que um erro ocorreu nas operações de escrita ou leitura, a função que estava realizando a operação registra o ocorrido num indicador de erro em `FILE`, você poderá consultar o erro invocando a seguinte função: [source,c] ---- int ferror(FILE *stream); ---- Haverá ocorrido erro caso o valor retornado for *diferente de zero*. ==== Atingiu o final do arquivo (((Arquivo, indicador final de arquivo))) Quando uma função atingiu o final do arquivo ela registra o ocorrido no indicador de final de arquivo, que pode ser consultado através da seguinte função: [source,c] ---- int feof(FILE *stream); ---- Um retorno diferente de zero indica que o final do arquivo foi atingido. === Lendo e escrevendo um carácter por vez Nesta seção vamos apresentar como ler e escrever no arquivo um carácter por vez. ==== Lendo um carácter com `fgetc` ou `getc` (((fgetc))) A função <> ler um carácter do arquivo, converte-o para `int` e retorna-o. Caso não haja mais carácteres disponível, devido ao final do arquivo, retorna a constante `EOF` (do inglês, _End of File_, que indica o final do arquivo): [source,c] ---- int fgetc(FILE *arquivo); ---- Após a leitura do carácter o indicador de posição de leitura do arquivo é incrementado em uma posição. (((getchar))) A função <> realiza a mesma operação, mas não requer parâmetro, utilizando a entrada padrão (`stdin`) como arquivo de entrada: [source,c] ---- int getchar(void); ---- NOTE: Perceba a função de ler um carácter não retorna um *`char`*, retorna um *`int`*. Isto porque a `EOF` não é um carácter que indica o final final do arquivo, mas uma constante do tipo `int` que a função de leitura de carácter retorna ao atingir o final do arquivo. ==== Escrevendo um carácter por vez com `fputc` e `putchar` (((fputc))) A função <> escreve um carácter (convertido para `unsigned char`) na posição atual do arquivo: [source,c] ---- int fputc(int caracter, FILE *stream); ---- A função retorna o carácter escrito ou `EOF` se houve erro durante a escrita. (((putchar))) A função <> realiza a mesma função que <> para opera somente na saída padrão (`stdout`): [source,c] ---- int putchar(int caracter); ---- ==== Exemplo completo de leitura e escrita O <> demonstra um a utilização das funções de leitura e escrita com carácteres: [[ex_lendo_e_escrevendo_linha]] .Lendo e escrevendo carácteres ==== :srcfile: lendo_e_escrevendo_char.c :saida: lendo_e_escrevendo_char.out .Código fonte {online}/{srcfile}[{local}/{srcfile}] [source, c] ---- include::{local}/{srcfile}[] ---- <1> Abertura dos arquivos para leitura e escrita respectivamente. <2> Verificando se os arquivos foram abertos com sucesso. <3> Ler um carácter da entrada e escreve o carácter lido na saída. <4> Verifica se atingiu o final do arquivo. <5> Fecha os arquivos abertos. .Saída da execução .... include::{local}/{saida}[] .... ==== === Lendo e escrevendo uma linha por vez com `fgets` e `fputs` Nesta seção vamos apresentar como ler e escrever no arquivo uma linha por vez. ==== Lendo uma linha com `fgets` (((fgets))) A função <> ler uma linha do arquivo e salva o conteúdo lido no `buffer` passado como parâmetro. O final de linha é retido e retornando junto com a linha lida. A função copia carácteres do arquivo até encontrar o final da linha, ou chegou ao final do arquivo ou leu `n-1` carácteres. O carácter `\0` é escrito após o último carácter lido, transformando `buffer` em um string terminado em zero, compatível com as funções <>. [source,c] ---- char *fgets(char * buffer, int n, FILE * arquivo); ---- A função retorna `buffer`, contendo a linha lida, ou `NULL` se houve algum erro durante a leitura. [NOTE] ==== Para entender o funcionamento da função <> é importante perceber como o computador funciona. Inicialmente alguém poderia desejar que esta função retornasse um string contendo a linha lida. Mas pense bem: Se se o arquivo lido for enorme e não possuir quebra de linhas, a operação de leitura de uma linha poderia retornar um string que não coubesse na memória. Como evitar que isto aconteça? Passando um parâmetro `n`, indicando a quantidade máxima de carácteres que deve ser lido na procura do final de linha. Além disso, para evitar erros de alocação de memória, é solicitado que o usuário forneça previamente o Buffer onde será copiado os carácteres da linha lida. ==== ==== Escrevendo um string com `fputs` (((fputs))) A função <> escreve um string, terminado com o carácter zero, no arquivo. [source,c] ---- int fputs(const char * string, FILE * arquivo); ---- A função retorna `EOF` em caso de erro. NOTE: Perceba que não há necessidade de especificar o tamanho na escrita, ela termina quando encontra o carácter nulo (`\0`). Outro fato importante é que *esta função não deve ser utilizado para escrita binária, pois ela não escreve o carácter nulo*. ==== Exemplo completo de leitura e escrita O seguinte código é um exemplo completo utilizando as funções de leitura e escrita com linha: .Lendo e escrevendo linhas ==== :srcfile: lendo_e_escrevendo_linha.c :saida: lendo_e_escrevendo_linha.out .Código fonte {online}/{srcfile}[{local}/{srcfile}] [source, c] ---- include::{local}/{srcfile}[] ---- <1> Ler uma linha da entrada. <2> Escreve o string lido na saída. .Saída da execução .... include::{local}/{saida}[] .... ==== === Lendo e escrevendo dados binários As funções de leitura e escrita de dados binários diferem dos arquivos de textos devido a ao conteúdo que será salvo e lido. ==== Lendo uma sequencia de elementos de um arquivo com `fread` (((fread))) A função <> ler uma sequência de elementos de um arquivo. Na função são passados como parâmetro o `buffer` onde será salvo a sequência lida, o `tamanho` do tipo de dado que será lido, a `quantidade` de elementos que serão lidos e o `arquivo` de onde será lido os dados. [source,c] ---- size_t fread(void * buffer, size_t tamanho, size_t quantidade, FILE * arquivo); ---- A função retorna a quantidade de elementos lidos, que pode ser menor do que `quantidade`, caso o final do arquivo foi encontrado ou houve algum erro durante a leitura. Se `quantidade` ou `tamanho` for zero, então `buffer` permanece inalterado e a função retorna zero. ==== Escrevendo uma sequencia de elementos de um arquivo com `fwrite` (((fwrite))) A função <> escreve uma sequência de elementos num arquivo. Na função são passados como parâmetro o `buffer` que é um ponteiro para o início da sequência de dados que se deseja escrever, o `tamanho` do tipo de elemento, a `quantidade` de elementos que se deseja escreve e o `arquivo` onde o conteúdo será escrito. [source,c] ---- size_t fwrite(const void * buffer, size_t tamanho, size_t quantidade, FILE * arquivo); ---- A função retorna o número de elementos que foram escritos, que pode ser menor do que `quantidade` somente se houve erro durante a escrita. Caso `quantidade` ou `tamanho` seja zero então o arquivo permanece inalterado e a função retorna zero. ==== Exemplo completo de leitura e escrita binária O seguinte código é um exemplo completo utilizando as funções de leitura e escrita apresentadas nesta seção. Alguns jogos mantém uma lista de recordistas, que salva o nome do jogador e pontuação obtida. Neste código criamos uma lista contendo a pontuação inicial de três jogadores fictícios e salvamos num arquivo. Em seguida, demonstramos a leitura do arquivo e finalmente imprimimos os recordes lidos. .Escrevendo e lendo elementos em binário ==== :srcfile: escrevendo_e_lendo_binario.c :saida: escrevendo_e_lendo_binario.out .Código fonte {online}/{srcfile}[{local}/{srcfile}] [source, c] ---- include::{local}/{srcfile}[] ---- <1> Declaração do tipo `Jogador`, com dois dados: seu nome e sua pontuação. <2> Inicialização de um array de recordistas iniciais que serão salvos mais adiante. <3> Salva os três recordistas no arquivo. <4> Ler os três recordistas do arquivo e salva no buffer `jogadores`. <5> Imprime os recordistas. .Saída da execução .... include::{local}/{saida}[] .... ==== === Descarregando o buffer com `fflush` (((fflush))) Alguns arquivos são abertos com buffers para escrita, isto implica que as operações de escritas no arquivo não são persistidas no arquivo até que o buffer esteja cheio ou o arquivo seja fechado. Para determinar que o conteúdo do buffer seja escrito no arquivo utilizamos a função <>: [source,c] ---- int fflush(FILE *stream); ---- A função retorna `EOF` caso houver erro na escrita do arquivo, caso contrário retorna zero. === Escrevendo e lendo dados formatados Finalizando as seções de escrita e leitura de dados, vamos conhecer funções provavelmente lhe são familiares. ==== Ler dados formatados (((fscanf)))(((scanf))) Para ler dados formatados você pode utilizar a função <>, que é similar a função <>, a única diferença é que `scanf` trabalha apenas com a entrada padrão, enquanto `sscanf` especifica o arquivo de entrada: [source,c] ---- int fscanf(FILE * arquivo, const char * formato, ...); ---- ==== Escrever dados formatados (((fprintf)))(((printf))) A escrita de dados formatados é feita através da função <>, que é similar a <> (que trabalha somente na saída padrão), a única diferença é que `fprintf` permite especificar o arquivo de saída: [source,c] ---- int fprintf(FILE * arquivo, const char * formato, ...); ---- === Movendo o indicador de posição Como foi apresentado anteriormente, o *((indicador de posição))* mantém o registro da posição atual do arquivo. Em alguns casos podemos ter a necessidade de avançar ou retroceder o ponto de leitura ou escrita do arquivo, as funções a seguir servem a este propósito. IMPORTANT: As funções `rewind`, `ftell` e `fseek` serão utilizadas no <>. Por enquanto é importante apenas que você reconheça a sua existência. ==== Retrocede para o início do arquivo com `rewind` (((rewind))) A função <> retrocede o indicador de posição para o início do arquivo: [source,c] ---- void rewind(FILE *arquivo); ---- ==== Lendo a posição atual (((ftell))) A função <> retorna a posição atual no arquivo: [source,c] ---- long int ftell(FILE *arquivo); ---- ==== Indo para o final do arquivo (((fseek))) Para ir para o final do arquivo invocamos podemo utilizar a função <> da seguinte forma: [source,c] ---- long fseek(arquivo, 0, SEEK_END); ---- === Recapitulando Neste capítulo foi apresentado os tipos de arquivos que existem, as similaridades entre fluxos e arquivos e como a linguagem abstrai os arquivos como fluxos. Em seguida aprendemos como abrir os arquivos antes de realizar operações neles, e como fechar para finalizar a operação. As operações para ler e escrever carácteres no arquivo possuiam um exemplo demonstrando sua utilização. Aprendemos que os arquivos possuem indicadores de erro, final de arquivo e de posição que são alterados durante as execuções das funções e leitura e escrita, e como consultar o estado destes indicadores. Vimos que existem funções próprias para trabalhar com arquivos de texto, que processam o arquivo por linhas e como utilizá-las. As funções de escrita e leitura de arquivos binários necessitam do tamanho do tipo de dado, a quantidade de dados que seriam processados e um buffer para realização das operações. Por último, vimos como alterar e consultar o indicador de posição do arquivo. //// Terminando arquivo com linha em branco ////