Subsecções

30 Arquivos

O armazenamento de dados em variáveis e arrays é temporário. Arquivos são usados para armazenamento permanente de grandes quantidades de dados (e programas) em dispositivos de armazenamento secundário, como discos.

Ás vezes não é suficiente para um programa usar somente a entrada e saída padrão. Há casos em que um programa deve acessar arquivos. Por exemplo, se nós guardamos uma base de dados com endereços de pessoas em um arquivo, e queremos escrever um programa que permita ao usuário interativamente buscar, imprimir e mudar dados nesta base, este programa deve ser capaz de ler dados do arquivo e também gravar dados no mesmo arquivo.

No restante desta seção discutiremos como arquivos de texto são manipulados em C++ . Como será visto, tudo ocorre de maneira análoga ao que acontece com entrada e saída padrão.

30.1 Acessando um arquivo: tipo fstream, funções open() e close()

C++ visualiza cada arquivo simplesmente como um fluxo (stream) seqüencial de bytes. Cada arquivo em C++ termina com um marcador de final de arquivo (end-of-file), definido pela constante EOF.

As regras para acessar um arquivo são simples. Antes que um arquivo seja lido ou gravado, ele é aberto. Um arquivo é aberto em um modo que descreve como o arquivo será usado (por exemplo, para leitura, gravação ou ambos). Um arquivo aberto pode ser processado por funções da biblioteca padrão em C++ . Estas funções são similares às funções de biblioteca que lêem e escrevem de/para entrada/saída padrão. Quando um arquivo não é mais necessário durante a execução do programa ele deve ser fechado. Ao final da execução de um programa todos os arquivos abertos são automaticamente fechados. Existe um número máximo de arquivos que podem ser simultaneamente abertos de forma que você deve fechar arquivos quando você não precisa mais deles em seu programa.

Quando um arquivo está aberto, um stream é associado ao arquivo. Este stream fornece um canal de comunicação entre um arquivo e o programa. Para definir tal stream, uma variável do tipo ofstream, ifstream ou fstream deve ser declarada. Esta variável irá ser usada para associar um arquivo em disco que tem um certo nome com as operações de leitura e gravação que veremos adiante. Três variáveis deste tipo e seus respectivos streams são abertos automaticamente quando um programa inicia sua execução: a entrada padrão (cin), a saída padrão (cout) e a saída padrão de erros (cerr).

Para abrir um arquivo, deve ser usada a função (método) open() associada à uma variável de um dos tipos stream. open() toma dois argumentos: o primeiro argumento é do tipo string e representa o nome do arquivo (por exemplo data.txt); o segundo argumento é a indicação do modo no qual o arquivo deve ser aberto. open() negocia com o sistema operacional e define o estado final do processo de abertura, que deve ser testado com o método fail()().

O programador/usuário não necessita saber detalhes de como a transferência de dados entre programa e arquivo é feita. As únicas sentenças necessárias no programa são a definição de uma variável do tipo stream e a abertura do arquivo. As sentenças abaixo dão um exemplo de como abrir o arquivo data.txt para leitura.

ifstream infile;
infile.open("data.txt");

O protótipo do método open() é:

<stream>.open(char *name);

open() recebe um argumento: uma string que é um nome de um arquivo a ser aberto. o tipo do stream indica a operação que será feita no arquivo: ofstream para escrita apenas, ifstream para leitura apenas, e fstream para leitura e escrita. Para este último tipo, o método open() recebe um segundo argumento, indicando o modo de abertura do arquivo: ios::in indica que o arquivo será aberto apenas para leitura; ios::out, para escrita apenas. Associado a estes valores podem ser usados os valores ios::app, ios::ate, ios::trunc, e ios::binary.

Se o arquivo não existe e é aberto para escrita, fopen() cria o arquivo. Se um arquivo já existente é aberto para escrita, o seu conteúdo é descartado. Há outros modos, incluindo anexação a um arquivo, leitura e escrita simultânea; para mais detalhes, veja a documentação da função nos livros-texto ou no manual on-line.

Para verificar se um arquivo é aberto com sucesso, usam-se os métodos fail() ou is_open(). Estas funções retornam o valor 1 (um) indicando respectivamente se houve falha na abertura ou se a abertura foi efetuada com sucesso. Alguns dos erros possíveis são: abrir um arquivo que não existe para leitura, abrir um arquivo para escrita quando não há mais espaço disponível em disco, ou abrir um arquivo para qualquer operação sendo que as permissões de acesso do arquivo não o permitem.

É recomendável que você teste se a abertura teve sucesso antes de continuar o programa. O trecho de programa abaixo ilustra como fazê-lo:

#include <iostream>
#include <fstream>

using namespace std;

.... 
fstream fp;
char fnome[13];

printf("Entre um nome de arquivo para abrir:");
cin >> fnome;

fp.open(fnome, ios::out);
if (fp.fail()) 
 {
   cout << "Erro na abertura de "<< fnome << " no modo escrita" << endl;
   return ;
 }
else
   cout << "Arquivo " << fnome << " aberto com sucesso no modo escrita" << endl;
...

No exemplo acima se o arquivo não puder ser aberto com sucesso, uma mensagem apropriada é exibida na saída padrão e o programa termina. Caso contrário uma mensagem indicando o sucesso na abertura do arquivo é exibida e o programa continua sua execução.

Cada arquivo aberto possui seu próprio stream. Por exemplo, se um programa vai manipular dois arquivos diferentes arq1.txt e arq2.txt simultaneamente (um para leitura e outro para escrita), dois streams devem ser declarados:

fstream fp1, fp2;

fp1.open("arq1.txt", ios::in);
fp2.open("arq2.txt", ios::out);

As variáveis do tipo stream estabelecem conexão entre o programa e o arquivo aberto. A partir do momento de abertura, o nome do arquivo é irrelevante para o programa. Todas as funções que operam sobre o arquivo usam o stream associado.

Terminada a manipulação do arquivo o programa deve fechar o arquivo. O método close() é usado com este propósito. Ela quebra a conexão entre o stream e o arquivo. O protótipo do método close() é:

<stream>.close();
Abaixo um exemplo de uso de fclose():
fstream fp1, fp2;

fp1.open("arq1.txt", ios::in);
fp2.open("arq2.txt", ios::out);

..........

fp1.close();
fp2.close();

30.2 Processando arquivos de texto

Arquivos podem guardar duas categorias básicas de dados: texto (caracteres codificados em ASCII ou UTF) ou binário (como dados armazenados em memória ou dados que representam uma imagem JPEG ou PNG).

Depois que um arquivo de texto é aberto, existem 3 formas diferentes de ler ou escrever sequencialmente os dados: (i) um caracter por vez, usando as funções da biblioteca padrão get() e put(); (ii) uma linha (string) por vez, usando gets() e puts(); e (iii) em um formato específico, usando os operadores $<<$() e $>>$(), como já fizemos com cout() e cin().

30.2.1 Entrada e saída de caracteres

As funções get() e put() operam sobre um arquivo aberto em modo de leitura.

Os protótipos de get() e put() are

<stream>.get();
<stream>.putc(char ch);

get() retorna o próximo caracter lido do arquivo representado pela stream, ou EOF se ocorrer final de arquivo ou um erro de leitura.

put() grava o caracter ch no arquivo representado pela stream. Esta função retorna o caracter gravado ou EOF.

Abaixo segue um exemplo de programa que lê um arquivo caracter a caracter e imprime o que foi lido na saída padrão (a tela do computador):

/************************************************************************
 * Lê um caracter por vez de um arquivo e
 * o imprime na saída padrão
 ************************************************************************/
#include <iostream> /* para funções padrão de E/S */
#include <fstream> /* para funções de E/S em arquivos */

using namespace std;

int main()
{
   ifstream fp; 
   char fnome[13];
   char ch;

   /* dialogo com usuário */
   cout << "Entre um nome de arquivo: ";
   cin >> fnome;

   fp.open( fnome, ios::in ); /* abre arquivo*/
   if (fp.fail()) {
      printf("Erro ao abrir %s\n", fnome);
      return;
      }
   else {
      printf("Arquivo aberto com sucesso.\n");

      /* Lê o arquivo caracter a caracter e imprime em stdout (saída padrão) */
      while( (ch = fp.get()) != EOF ) 
          cout << ch; 

      fp.close(); /* fecha arquivo */
   }
}

Observe o tipo da variável ch. Mude o tipo para int e execute novamente o programa (após compilação). Você pode exlicar a diferença nos resultados?


Créditos: Documento produzido pelo Prof. Armando L.N. Delgado (DINF/ET/UFPR), baseado em revisão sobre material de Prof. Carmem Hara e Prof. Wagner Zola (DINF/ET/UFPR).

Esta obra está licenciada com uma Licença Creative Commons Atribuição-NãoComercial-CompartilhaIgual 4.0 Internacional.  Licença Creative Commons

Armando Luiz Nicolini Delgado
2020-10-20