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.
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:
|
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.
Para abrir arquivo, deve-se usar a função fopen, definida na
biblioteca stdio.
|
abre arquivo de nome nomeDoArquivo para realizar operações
de entrada ou saída, conforme especificado por modo:
nomeDoArquivo denota uma cadeia de caracteres que
representa o nome de um arquivo, incluindo possivelmente o
caminho para o arquivo, emodo denota uma cadeia de caracteres que representa o
modo como o arquivo deve ser aberto, tipicamente "r" para
operações de leitura em arquivos-texto ou "w" para
operações de escrita em arquivos-texto.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:
O sufixo "+" deve ocorrer após o sufixo "b" que indica arquivos binários; por exemplo, para abertura de arquivos binários para operações tanto de leitura quanto de escrita devem ser usados os modos "rb+", "wb+" ou "ab+".
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:
|
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.
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:
|
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.
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.
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:
|
é equivalente a:
|
De modo similar, o uso de printf é equivalente a fprintf
com argumento stdout. Por exemplo:
|
é equivalente a:
|
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.
|
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:
|
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:
|
poderíamos usar:
|
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).
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:
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.
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.
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.
Solução:
|
Solução:
|
Solução:
|
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.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.