Jiraspiom

Blog Pessoal

Lição 16

Lição 16 – I/O com arquivos

Entrada/Saída com Arquivos

Temos à nossa disposição as seguintes classes em C++ para relizar operações de entrada e saída de caracteres de/para arquivos:

  • ofstream: classe Stream para escrever em arquivos.
  • ifstream: classe Stream para ler de arquivos.
  • fstream: classe Stream para ambos ler e escrever de/para arquivos.

Essas classes são derivadas direta ou indiretamente de classes istream e ostream. Já usamos objetos cujos tipos eram essas classes: cin é um objeto da classe istream e cout é um objeto da classe ostream. Assim, já viemos utilizando classes que são relacionadas a nosso arquivo stream. E, de fato, podemos usar nosso arquivo streams da mesma maneira que já fizemos para usar cin e cout, com a diferença que temos que associar essas streams com arquivos físicos. Vejamos um exemplo:

#include <iostream>
#include <fstream>
using namespace std;

int main () {
ofstream marquivo;
marquivo.open (“exemplo.txt”);
marquivo << “Escrevendo em um arquivo.\n”;
marquivo.close();
return 0;
}

, o que resulta numa escrita num arquivo:

[file exemplo.txt]
Escrevendo em um arquivo

Este código cria um arquivo chamado exemplo.txt e insere uma sentença nele da mesma maneira que fazíamos com cout, mas usando o arquivo stream meuarquivo como saída.

Explicando por partes

A primeira operação geralmente realizada em um objeto de uma dessas classes é associá-la a um arquivo real. Este procedimento é conhecido como “abrir um arquivo”. Um arquivo aberto é representado em um programa por um objeto stream (uma instanciação de uma dessas classes, no exemplo anterior: meuarquivo) e qualquer operação de entrada ou saída realizada neste objeto stream será aplicada ao arquivo físico associado a ela.

Para abrir um arquivo com um objeto stream usamos sua função membro open():

open (nome_do_arquivo, mode);

, onde nome_do_arquivo é uma seqüência de caracteres terminadas em nulo do tipo const char * representando o nome de um arquivo para ser aberto, e mode é um parâmetro opcional com uma combinação das seguintes flags:

ios::in Open (Abrir) para operações de entrada
ios::out Open para operações de saída
ios::binary Open para modo binário
ios::ate Seta a posição inicial no fim do arquivo
Se este flag não está setado com nenhum valor, a posição inicial é o início do arquivo.
ios::app Todas as operações de saída são realizadas no fim do arquivo, anexando o conteúdo ao conteúdo atual do arquivo. Este flag pode ser usado apenas em streams Open para operações de saída.
ios::trunc Se o arquivo aberto para operações de saída já existirem antes, seu conteúdo anterior é deletado e substituído pelo novo.

Todos estes flags podem ser combinados usando o operador OR (|). Por exemplo, se quisermos abrir o arquivo exemplo.bin no modo binário para adicionar dados, podemos usar a seguinte chamada à função membro open():

ofstream marquivo;
marquivo.open (“exemplo.bin”, ios::out | ios::app | ios::binary);

Cada uma das funções membro open() das classes ofstream, ifstream e fstream tem um modo padrão que é usado se o arquivo é aberto sem um segundo argumento:

classe parâmetro mode padrão
ofstream ios::out
ifstream ios::in
fstream ios::in | ios::out

Para classes ifstream e ofstream, ios:in e ios:out são automaticamente e respectivamente assumidos, mesmo se um modo que não os inclui é passado como segundo argumento para a função membro open(). O valor padrão apenas é aplicado se a função é chamada sem especificar nenhum valor para o parâmetro mode. Se a função é chamada com qualquer valor naquele parâmetro o modo pardão é sobrescrito, não combinado.

Arquivo streams abertos em modo binário realizam operações de entrada e saída independentemente de quaisquer considerações de formato. Arquivos não binários são conhecidos como “arquivos texto”, e algumas traduções devem ocorrer devido à formatação de alguns caracteres especiais (como newline).

Como a primeira tarefa que é realizada em um arquivo objeto stream é geralmente abrir um arquivo, estas três classes incluem um construtor, que chama automaticamente a função membro open() e tem exatamente os mesmos parâmetros que este membro. Assim, poderíamos também ter declarado o objeto marquivo anterior e conduzido a mesma operação de abertura no exemplo fazendo:

ofstream marquivo (“exemplo.bin”, ios::out | ios::app | ios::binary);

Combinando construção do objeto e abertura de stream em uma mesma expressão. Ambas as formas de abrir um arquivo são válidas e equivalentes.

Para checar se um arquivo stream teve sucesso ao abrir um arquivo, podemos fazer isto chamando o membro is_open() sem argumentos. Ela retorna um valor booleano de true, no caso em que realmente o objeto stream for associado com um arquivo aberto, ou false em caso contrário.

if (marquivo.is_open()) { /* ok, prossiga com a saida*/ }

Fechando um arquivo

Quando finalizamos com nossas operações de entrada e saída em um arquivo, devemos fechá-lo para que ses recursos se tornem disponíveis novamente. Para isso, temos que chamar a função membro strem close(). Esta função membro não recebe parâmetros, e o que ela faz é esvaziar os buffers e fechar o arquivo:

marquivo.close();

uma vez que esta função membro é chamada, o objeto stream pode ser usado para abrir um outro arquivo, e o arquivo está disponível para ser aberto por outros processos. No caso em que um objeto é destruído enquanto ainda é associado a um arquivo aberto, o destrutor automaticamente chama a função membro close().

Arquivos Texto

Arquivos Texto são aqueles que não incluem a flag ios::binary em seu modo de abertura. Estes arquivos são projetados para armazenar texto e deste modo todos os valores de entrada/saída que inserirmos/retirarmos deles podem sofrer alguma transformação de formatação, que não necessariamente correspondem a seus valores binários literais.

Operações de saída de dados em arquivos texto são realizadas da mesma maneira que operamos com cout:

#include <iostream>
#include <fstream>
using namespace std;

int main () {
ofstream marquivo (“exemplo.txt”);
if (marquivo.is_open())
{
marquivo << “Uma linha.\n”;
marquivo << “Outra linha.\n”;
marquivo.close();
}
else cout << “Incapaz de abrir o arquivo”;
return 0;
}

, o que resultaria em:

[file exemplo.txt]
Uma linha.
Outra linha.

Entrada de dados de um arquivo também pode ser realizada da mesma maneira que fizemos com o cin:

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main () {
string linha;
ifstream marquivo (“exemplo.txt”);
if (marquivo.is_open())
{
while (! marquivo.eof() )
{
getline (marquivo,linha);
cout << linha << endl;
}
marquivo.close();
}

else cout << “Incapaz de abrir arquivo”;

return 0;
}

, resultando:

Uma linha.
Outra linha.

Este último exemplo lê um arquivo texto e imprime seu conteúdo na tela. Note como usamos uma nova função membro, chamada eof(), que retorna true no caso de o fim do arquivo ter sido atingido. Criamos um loop while que termina quando marquivo.eof() se torna true (fim de marquivo atingido).

Flags de estado

Além de eof(), outras funções existem para checar o estado de uma stream (todas elas retornam um valor booleano):

  • bad() – Retorna true se uma operação de ler ou escrever falha. Por exemlo, no caso em que tentamos escrever em um arquivo que não está aberto para escrita ou o dispositivo onde tentamos escrever não tem espaço disponível.
  • fail() – Retorna true nos mesmo casos que bad(), mas também no caso em que um erro de formato acontece, como quando um caracter alfabético é extraído quando tentamos ler um número inteiro.
  • eof() – retorna true se um arquivo aberto para leitura atinge o fim.
  • good()– É a flag de estado mais genérica: retorna false nos mesmos casos em que chamar qualquer uma das funções anteriores retornaria true.

Para resetar as flags de estado checadas por qualquer uma destas funções membro, vemos que podemos usar a função membro clean(), que nao recebe parâmetros.

Ponteiros stream get e put

Todos os objetos streams i/o têm, no mínimo, um ponteiro stream interno:

  • ifstream, como istream, tem um ponteiro conhecido como ponteiro get que aponta para o elemento a ser lido na próxima operação de entrada.
  • ofstream, como ostream, tem um ponteiro conhecido como ponteiro put que aponta para a o local onde o próximo elemento tem de ser escrito.
  • fstream, herda ambos ponteiros get e put, de iostream (que é derivada de ambos istream e ostream).

Estes ponteiros stream internos que apontam para o local de leitura e escrita podem ser manipulados usando as seguintes funções membro:

tellg() e tellp()

Estas duas não têm parâmetros e retornam um valor do tipo membro pos_type, que é um dado inteiro representando a posição atual do ponteiro stream get (no caso de tellg) ou do ponteiro stream put (no caso de tellp).

seekg() and seekp()

Estas funções nos permite mudar a posição dos ponteiros stream get e put. Ambas as funções são sobrecarregadas com dois diferentes de protótipos
O primeiro é:

seekg ( posicao );
seekp ( posicao );

Ao usar este protótipo, o ponteiro stream é trocado para a posição absoluta posicao (contando do início do arquivo). o tipo para este parâmetro é o mesmo que o retornado pelas funções tellg e tellp: o

Using this prototype the stream pointer is changed to the absolute position position (counting from the beginning of the file). The type for this parameter is the same as the one returned by functions tellg and tellp.

O outro protótipo para estas funções é:

seekg ( offset, direcao );
seekp ( offset, direcao );

Ao usar este protótipo, a posição do ponteiro get e put é setada para um valor offset relativo a algum ponto específico determinado pelo parâmetro direcao. Offset é do tipo membro off_type, que é também um tipo inteiro. E direcao é do tipo seekdir, que é um tipo enumerado (enum) que determina o ponto de onde offset é contado, e pode ter qualquer um dos seguintes valores:

ios::beg offset contado do começo da stream
ios::cur offset contado da posição atual do ponteiro stream
ios::end offset contado do fim da stream


O exemplo seguinte usa as funções membro que acabamos de ver para obter o tamanho de um arquivo:

#include <iostream>
#include <fstream>
using namespace std;

int main () {
long comeco,fim;
ifstream marquivo (“exemplo.txt”);
comeco = marquivo.tellg();
marquivo.seekg (0, ios::end);
fim = marquivo.tellg();
marquivo.close();
cout << “Tamanho é: ” << (fim-comeco) << ” bytes.\n”;
return 0;
}


, que mostra na tela:

Tamanho é: 40 bytes.

Arquivos binários

Em arquivos binários, inserir e retirar dados com os operadores de extração e inserção (<< e >>) e funções como getline não é eficiente, já que não precisamos formatar nenhum dado, e dados não usam códigos de separação usados por arquivos textos para separar elementos (como space, newline, etc).

Arquivos stream incluem duas funções membro especificamente projetadas para entrada e saída de dados binária seqüenciamente: write e read. A primeira é uma fnução membro de ostream herdada por ofstream. E red é uma função membro de istream herdada de ifsttream. Objetos ca dlasse stream têm ambos os membros. Seus prrotótipos:

write ( bloco_de_memoria, tamanho );
read ( bloco_de_memoria, tamanho );


, onde bloco_de_memoria é do tipo “ponteiro para char” (char*), e representa o endereço de um array de bytes onde os elementos de leitura de dados são armazenados ou de onde os elementos de dados a serem escritos são tirados. O parâmetro tamanho é um valor inteiro que especifica o número de caracteres a ser lido ou escrito de/para o bloco de memória.

// lendo um arquivo binario completo
#include <iostream>
#include <fstream>
using namespace std;

ifstream::pos_type tamanho;
char * blocomem;

int main () {
ifstream arquivo (“exemplo.txt”, ios::in|ios::binary|ios::ate);
if (arquivo.is_open())
{
tamanho = arquivo.tellg();
blocomem = new char [size];
arquivo.seekg (0, ios::beg);
arquivo.read (blocomem, size);
arquivo.close();

cout << “O conteudo completo do arquivo está na memoria.”;

delete[] blocomem;
}
else cout << “Incapaz de abrir o arquivo”;
return 0;
}


, o que retorna na tela:

O conteudo completo do arquivo está na memoria.


Neste exemplo, o arquivo inteiro é lido e armazenado em um bloco de memória. Isto é feito da seguinte maneira: primeiro o arquivo é aberto com o flag ios::ate, que significa que o ponteiro será posicionado no final do arquivo. Desta maneira, quando chamamos o membro tellg(), obtemos diretammente o tamanho do arquivo. Note o tipo que usamos para declarar o tamanho da variável:

ifstream::pos_type tamanho;


O tipo ifstream::pos_type é um tipo especificousado para posicionamento de arquivos e buffers e é o tipo retornado por arquivo.tellg(). Este tipo é definido como um tipo inteiro, assim podemos conduzir nele as mesmas operações que conduzimos em quaisquer outros valores inteiros, e podem ser convertidos com segurança para outro tipo inteiro extenso o bastante para conter o tamanho do arquivo. Para um arquivo com um tamanho menor que 2GB poderíamos usar int:

int tamanho;
tamanho = (int) arquivo.tellg();


Uma vez que obtemos o tamanho do arquivo, requerimos a alocação de um bloco de memória extenso o bastante para caber o arquivo inteiro:

blocomem = new char[size];


Logo após isto, procedemos setamos o ponteiro get no começo do arquivo (lembrando que abrimos o arquivo com este ponteiro no fim), então lemos o arquivo inteiro e, finalmente, o fechamos:

arquivo.seekg (0, ios::beg);
arquivo.read (blocomem, tamanho);
arquivo.close();


Neste ponto podemos operar com os dados obtidos do arquivo. Nosso programa simplesmente anuncia que o conteúdo do arquivo está na memória e então termina.

Buffers e Sincronização

Quando operamos com streams de arquivo, elas são associadas a um buffer interno do tipo streambuf. Este buffer é um bloco de memória que atua como um intermediário entre a stream e o arquivo físico. Por exemplo, com uma ofstream, cada vez que a função membro put (que escreve um caracter único) é chamada, o caracter não é escrito diretamente ao arquivo físico com que a stream é associada. Ao invés disso, o caracter é inserido naquele buffer intermediário da stream.

Quando o buffer lota, todos os dados contidos nele são escritos no meio físico (se é uma stream de saída) ou simplesmente limpado (se é uma stream de entrada). Este processe é chamado sincronização e acontece nas seguintes circunstâncias:

  • Quando o arquivo é fechado: antes de fechar um arquivo, todos os buffers que ainda não foram lotados são sincronizados e todos os dados pendentes são escritos ou lidos para o meio físico.
  • Quando o buffer está cheio: buffers têm um tamanho limitado. Quando o buffer está cheio, ele é automaticamente sincronizado.
  • Explicitamente, com manipuladores: quando certos manipuladores são usados em streams, uma sincronização explícita acontece. Estes manipuladores são: flush e endl.
  • Explicitamente, com funções membro sync(): chamando a função membro da stream sync(), que não recebe parâmetros, causa uma sincronização imediata.

Esta função retorna um valor int igual a -1 se a stream não tem buffer associado ou no caso de falha. Em caso contrário (se o stream buffer foi sucessivamente sincronizado) ela retorna 0.

Anúncios
%d blogueiros gostam disto: