Previous Up Next
Programação de Computadores em C

Capítulo 9  Entrada e saída em Arquivos

Este capítulo descreve como realizar operações de leitura e escrita de dados em arquivos, em vez de em dispositivos de entrada e saída (E/S) padrões.

Como mostramos na seção 3.2.1, a entrada e escrita de dados em dispositivos de E/S padrões podem ser redirecionadas para serem feitas em arquivos. A desvantagem da E/S em arquivos com redirecionamento é que tanto o arquivo de entrada quanto o arquivo de saída de dados devem ser únicos e ambos determinados antes da execução do programa. Ao contrário, a E/S comum em arquivos definida na linguagem C permite que operações de E/S de dados sejam realizadas em diversos arquivos, bastando para isso que esses arquivos sejam abertos e fechados de acordo com o que for necessário, durante a execução de um programa.

Para realizar operações de E/S em arquivos, a linguagem C provê o tipo FILE (seção 9.1), usado para acesso a arquivos, operações de abertura e fechamento de arquivos (seção 9.2) e operações específicas de leitura e escrita em arquivos (seção 9.3).

Essas seções tratam das operações necessárias para manipulação de arquivos em geral, que são as seguintes, nesta ordem: declarar variável de tipo ponteiro-para-arquivo, abrir um arquivo, realizar operações de leitura ou escrita sobre o arquivo e fechar o arquivo.

9.1  Tipo e Variáveis para Acesso a Arquivos

A linguagem C provê um tipo pré-definido FILE, para acesso a arquivos. Uma variável usada para acesso a um arquivo deve ter tipo ponteiro-para-arquivo. Por exemplo, o comando de declaração:

FILE *arq;

cria uma variável de nome arq que é um ponteiro para um arquivo. Com essa declaração, a variável arq poderá ser usada em comandos subsequentes em operações de E/S em arquivos, como descrito a seguir.

A biblioteca stdio da linguagem C define três variáveis pre-definidas para acesso a arquivos, que são disponíveis para uso em operações de entrada ou saída diretamente, sem necessidade de serem abertos (abertura e fechamento de arquivos são descritos na seção seguinte): stdin (entrada padrão), stdout (saída padrão) e stderr (dispositivo padrão de erro).

O dispositivo de entrada padrão é usualmente o teclado (de onde são lidos os dados, quando se usa a função scanf), a menos que um redirecionamento seja feito.

O dispositivo de saída padrão é usualmente a tela (onde os dados são escritos, quando se usa a função printf), a menos que um redirecionamento seja feito.

O dispositivo padrão de erro é onde mensagens de erro devem ser escritas. Usualmente, esse dispositivo é o mesmo que o dispositivo de saída padrão, a menos que um redirecionamento seja feito. Note que redirecionamentos dos dispositivos padrões de erro e de saída são independentes. Por exemplo, um redirecionamento da saída padrão não altera o dispositivo padrão de erro.

9.2  Abertura e Fechamento de arquivos

9.2.1  Abertura de arquivos

Para abrir arquivo, deve-se usar a função fopen, definida na biblioteca stdio.

arq = fopen(nomeDoArquivo, modo);

abre arquivo de nome nomeDoArquivo para realizar operações de entrada ou saída, conforme especificado por modo:

Arquivos podem ser arquivos-texto ou arquivos binários. Arquivos-texto são arquivos contendo caracteres, e arquivos binários são arquivos contendo valores de outros tipos. Arquivos-texto são arquivos legíveis, uma vez que consistem de uma sequência de caracteres legíveis, ao passo que arquivos binários são ilegíveis, uma vez que contém dados binários, como representados na memória de computadores.

O modo "r" (abreviação de read) requer que o nome do arquivo especificado exista, para que o arquivo seja aberto para leitura.

O modo "w" (abreviação de write) cria um novo arquivo com o nome especificado; caso o arquivo já exista, os dados de operações de escrita realizadas no arquivo irão ser realizados a partir do início do arquivo; os dados existentes são perdidos.

Há outros modos de abrir um arquivo além de simplesmente para leitura ("r") ou escrita ("w") em arquivos-texto:

A função fopen retorna o ponteiro NULL se ocorrer algum erro na abertura do arquivo. Por exemplo, o valor NULL é retornado se um arquivo não existente for aberto para leitura, com modo "r".

O exemplo a seguir contém vários comandos de abertura de arquivos, usando fopen:

FILE *arqEntrada, *arqSaida; char *modo = "r"; char nomeArqSaida[] = "saida.txt"; arqEntrada = fopen("entrada.txt", modo); if (arqEntrada == NULL) printf("entrada.txt nao existe ou nao pode ser aberto.\n"); else { arqSaida = fopen(nomeArqSaida, "w"); if (arqSaida == NULL) printf(" %s nao pode ser aberto.\n", nomeArqSaida); }

Repetindo: ao usar o modo "r", o arquivo precisa existir, ao passo que, ao usar o modo "w", o arquivo é criado para realização de operações de escrirta e, se ele existir, seu conteúdo anterior às operações de escrita será perdido.

9.2.2  Fechamento de arquivos

O fechamento de um arquivo é feito com a função fclose. O argumento é um ponteiro-para-arquivo. Sendo arq variável de tipo FILE*, o fechamento do arquivo arq é feito simplesmente usando:

fclose(arq);

O fechamento de uma arquivo pode ser especialmente importante no caso de arquivos de saída. O motivo é que as operações de saída são usualmente realizadas, por questão de eficiência, em áreas temporárias da memória (chamadas, em inglês, de "buffers"), antes de serem transferidas para o arquivo. Somente quando o arquivo é fechado ou quando não há mais espaço na área temporária é que a operação de escrita no arquivo é efetivamente realizada.

Desta forma, se o fechamento de um arquivo não for realizado, os dados podem não aparecer no arquivo de saída, apesar de terem sido escritos na área temporária.

9.3  Leitura e Escrita em Arquivos

Depois de aberto, as operações de leitura ou escrita podem ser realizadas. Para leitura e escrita em arquivos-texto, são usadas, respectivamente, fscanf e fprintf. Para leitura e escrita em arquivos binários, são usadas, respectivamente, fread e fwrite.

9.3.1  Leitura e Escrita em Arquivos Textuais

As funções fscanf e fprintf, usadas para leitura e escrita em arquivos-texto, têm comportamento similar a scanf e printf\/ (seção 3.2); a única diferença é que têm um parâmetro adicional, que é o primeiro parâmetro, de tipo FILE *, que indica o arquivo no qual a operação (de leitura ou escrita) será realizada.

O uso de scanf é equivalente a fscanf com argumento stdin. Por exemplo:

scanf("%d", &val);

é equivalente a:

fscanf(stdin,"%d", &val);

De modo similar, o uso de printf é equivalente a fprintf com argumento stdout. Por exemplo:

printf("%d", val);

é equivalente a:

fprintf(stdout,"%d", val);

O programa abaixo é um exemplo de programa de uso de fprintf. O programa lê diversos inteiros do dispositivo de entrada padrão, usando scanf, até que ocorra fim dos dados, e imprime os inteiros lidos em um arquivo, de nome "dados.txt", no diretório corrente.

#include <stdio.h> #include <stdlib.h> int main () { int val, testeFim, FILE *arq = fopen("dados.txt"); if (arq != NULL) while (1) { testeFim = scanf("%d", val); if (testeFim == EOF) break; fprintf(arq, val); } return EXIT_SUCCESS; } }

Como já descrito no Exercício Resolvido 11, seção 4.4, uma chamada a scanf retorna o número de leituras realizadas com sucesso (número de valores lidos com sucesso); no caso de não haver mais nenhum dado a ser lido, ou seja, no caso de indicação de fim dos dados, a chamada retorna EOF, definido na biblioteca stdio como sendo igual a -1.

Um problema que pode ocorrer em testes de fim-de-arquivo (EOF), como feito no programa acima, é que se um erro ocorrer em uma operação de leitura — por exemplo, se uma letra for encontrada em uma operação de leitura quando um número inteiro está sendo esperado (como em scanf("%d",val); acima) — o erro não será tratado corretamente. Nesse caso, o valor retornado por scanf será igual a zero (não ocorreu nenhuma leitura com sucesso) e a posição de leitura no arquivo de entrada não será alterada. Isso faz com que o programa acima fique, nesse caso, em ciclo infinito (indefinidamente executando os comandos internos ao comando while). Uma maneira adequada de evitar isso é testar o número de leituras realizadas com sucesso, em vez de testar apenas se scanf retorna EOF. No exemplo, o número de leituras deve ser igual a 1. O programa ficaria então como a seguir:

#include <stdio}.h> #include <stdlib}.h> int main () { int val, numLeituras, FILE *arq = fopen("dados.txt"); if (arq != NULL) while (1) { numLeituras = scanf("%d",&val); if (numLeituras != 1) break; fprintf(arq, val); } return EXIT_SUCCESS; }

Uma outra maneira de testar fim-de-arquivo é com a função feof, definida na biblioteca stdio. O argumento deve ser de tipo ponteiro-para-arquivo. O resultado é verdadeiro se e somente se a posição de leitura está no final do arquivo. No exemplo acima, em vez de:

if (arq != NULL) while (1) { testeFim = scanf("%d", &val); if (testeFim == EOF) break; fprintf(arq}, val); }

poderíamos usar:

if (arq != NULL) while (feof(arq)) { scanf("%d, &val); fprintf(arq, val); }

Entretanto, como mencionamos, é preferível nesse caso testar o resultado da chamada de scanf para evitar erros de leitura de dados que sejam relacionados aos próprios dados sendo lidos em vez de serem referentes a não existência de dados a serem lidos (isto é, em vez de serem referentes a fim-de-arquivo).

9.3.2  Leitura e Escrita em Arquivos Binários

As funções fread e fwrite, usadas respectivamente para leitura e escrita em arquivos binários, têm ambas 4 argumentos, descritos a seguir (cada item se refere a dados a serem lidos, no caso de fread, ou escritos, no caso de fwrite:

  1. Endereço da área de memória — no qual será lido o valor (no caso de fread), ou do qual será copiado o valor (no caso de fwrite).

    O endereço é usualmente determinado com uso de &v, onde v é o nome de uma variável, como acontece quando usamos scanf.

  2. Tamanho dos elementos.

    Esse argumento é usualmente determinado por meio do uso de sizeof. Para escrever o valor contido em — ou ler e armazenar um valor em — uma váriavel x, pode-se usar sizeof(x). Em geral no entanto é mais comum e adequado o uso de sizeof(t), onde t é o tipo dos elementos.

  3. Número de elementos. Para ler ou escrever um arranjo de elementos de tipo t, pode-se passar o número de elementos do arranjo. Em outros casos, o número de elementos é igual a 1.
  4. O ponteiro-para-arquivo usado para acesso ao arquivo.

Ambas as funções fread e fwrite podem ser usadas para ler ou escrever quaisquer tipos de dados, como arranjos e registros.

A função fread retorna o número de elementos realmente lidos. Por exemplo, se fread for usado para ler um arranjo de 100 inteiros mas o arquivo só contém 30 inteiros, o valor retornado será 30.

Para verificar se o fim do arquivo foi alcançado, a função feof pode ser usada. A função feof recebe um ponteiro-para-arquivo e retorna verdadeiro se e somente se a posição corrente (de leitura ou escrita no arquivo) é a do fim do arquivo.

Considere os seguintes exemplos de programas que realizam operações de E/S em arquivos binários. O primeiro lê vários inteiros do dispositivo de entrada padrão e escreve esses inteiros em um arquivo binário. O segundo programa lê, do dispositivo de entrada padrão, nomes de arquivos binários, com valores inteiros, de entrada e saída, e copia o conteúdo do arquivo de entrada para o de saída. O terceiro programa lê, do dispositivo de entrada padrão, um nome de arquivo e mostra o conteúdo desse arquivo no dispositivo de saída padrão.

9.4  Exercícios Resolvidos

  1. Escreva um programa que leia inteiros do dispositivo de entrada padrão, e escreva esses inteiros em um arquivo binário, de nome "dados.int".

    Solução:

    #include <stdio.h> #include <stdlib.h> int main () { FILE *arq = fopen("dados.int", "wb"); int n, v; while (1) { n = scanf("%d", &v); if (n != 1) break; fwrite(&v, sizeof(int), 1, arq); } fclose(arq); }
  2. Escreva um programa que leia, do dispositivo de entrada padrão, dois nomes de arquivos (nomes com no máximo 29 caracteres), e copie o conteúdo do primeiro arquivo binário, de valores inteiros, para o segundo. O programa termina quando não há mais dados a serem lidos.

    Solução:

    #include <stdio.h> #include <stdlib.h> int main () { char max = 30; char *nomeArqEnt = malloc(max * sizeof(char)), *nomeArqSai = malloc(max * sizeof(char)); scanf("Nome do arquivo de entrada: %s", &nomeArqEnt); scanf("Nome do arquivo de saída: %s", &nomeArqSai); FILE *arqEnt = fopen(nomeArqEnt,"rb"), *arqSai = fopen(nomeArqSai,"wb"); int v, n; while (1) { n = fread(&v, sizeof(int), 1, arqEnt); if (n > 0) fwrite(&v, sizeof(int), 1, arqSai); else break; } fclose(arqEnt); fflush(arqSai); fclose(arqSai); }
  3. Escreva um programa que leia, do dispositivo de entrada padrão, o nome de um arquivo binário, contendo valores inteiros, e imprima, no dispositivo de saída padrão, os valores inteiros contidos nesse arquivo, um por linha. O nome do arquivo de entrada tem no máximo 29 caracteres.

    Solução:

    #include <stdio.h> #include <stdlib.h> int main () { char max = 30; char nomeArq = malloc(max * sizeof(char)); scanf("Nome do arquivo de entrada: %s", nomeArq); FILE *arq = fopen(nomeArq,"rb"); int n, v; while (1) \{ n = fread(&v, sizeof(int), 1, arq); if (n > 0) printf("%d\n",v); else break; } fflush(arq); fclose(arq); }

9.5  Exercícios

  1. Escreva um programa que leia, do dispositivo de entrada padrão, os nomes de dois arquivos e imprima mensagem indicando se os conteúdos dos dois arquivos, que você pode considerar que são arquivos-texto, são iguais entre si ou não. Além disso, se não forem iguais, a mensagem deve conter, além da informação de que são diferentes, qual é a posição do primeiro caractere distinto encontrado, e quais são estes caracteres distintos, nos dois arquivos. A posição i é a do i-ésimo caractere (posições começam de 1). O programa deve testar se os arquivos existem e emitir mensagem apropriada, no dispositivo padrão de erro, caso um deles ou ambos não existam.
  2. Escreva um programa que leia, do dispositivo de entrada padrão, os nomes de dois arquivos e copie o conteúdo do primeiro arquivo para o segundo, de modo que no final o segundo seja uma cópia do primeiro. Considere que os arquivos são arquivos-texto. O programa deve testar se o arquivo de entrada existe e emitir mensagem apropriada, no dispositivo padrão de erro, caso não exista.
  3. Escreva um programa que leia, do dispositivo de entrada padrão, os nomes de três arquivos-texto, digamos arq1, arq2 e arq3, e crie um arquivo, de nome arq3, cujo conteúdo seja igual ao conteúdo de arq1 seguido do conteúdo de arq2. O programa deve emitir mensagem no dispositivo padrão de erro caso arq1 ou arq2 não exista ou outro erro ocorrer na abertura dos arquivos.
  4. Escreva um programa que leia, do dispositivo de entrada padrão, o nome de um arquivo-texto, um valor inteiro positivo n, e em seguida várias cadeias de caracteres de tamanho máximo n, e imprima, para cada cadeia lida, o número de vezes que ela ocorre no arquivo.

    O programa deve ler cada cadeia e imprimir o número de vezes que ela ocorre no arquivo (não deve ler todas as cadeias antes de imprimir o número de vezes que cada uma ocorre no arquivo).

    O programa deve emitir mensagem, no dispositivo padrão de erro, caso o arquivo-texto não exista.


Previous Up Next