ifstream
e ofstream
. Uma variável de um destes tipos representa uma ligação
entre um ficheiro e o nosso programa, por isso usa-se aqui o termo "canal" para
referir instâncias destas classes.Ao criar um canal é comum (e desejável) indicar imediatamente o nome do ficheiro a que esse canal estará ligado. Por exemplo, para construir um canal de entrada (que apenas permite operações de extracção) pode-se usar as seguintes instruções:
ou
ifstream canal_de_entrada("um nome de um ficheiro qualquer");
ou
string nome_do_ficheiro = "um nome de um ficheiro qualquer";
ifstream canal_de_entrada(nome_do_ficheiro.c_str());
ou de modo semelhante, para criar um canal de saída (que apenas permite operações de inserção):
string nome_do_ficheiro;
cin >> nome_do_ficheiro;
ifstream canal_de_entrada(nome_do_ficheiro.c_str());
ou
ofstream canal_de_saida("um nome de um ficheiro qualquer");
ou
string nome_do_ficheiro = "um nome de um ficheiro qualquer";
ofstream canal_de_saida(nome_do_ficheiro.c_str());
Ao construir um canal de saída, como foi feito no exemplo acima, se existir um ficheiro com o nome indicado, toda a informação que este contém é eliminada.
string nome_do_ficheiro;
cin >> nome_do_ficheiro;
ofstream canal_de_saida(nome_do_ficheiro.c_str());
Depois de construído um canal de entrada ou saída pode-se verificar se este se encontra num estado de erro usando-o como se fosse uma variável booleana:
Um canal de entrada de dados pode passar a um estado de erro por várias razões. As mais comuns são:
if(not canal_de_entrada)
cerr << "O canal de entrada encontra-se num estado "
<<"de erro não podendo ser feitas operações de leitura."
<< endl;
cin
e
cout
(que são do tipo istream
e
ostream
respectivamente), por exemplo:
Num ficheiro, quando se guardam colecções de itens, é conveniente escrever primeiro o número de itens guardados, de modo a facilitar a leitura, que nesse caso pode ser feita como no exemplo seguinte:
string uma_string_qualquer;
int um_inteiro_qualquer;
/*
A extracção feita deste modo ignora espaços, tabuladores e fins-de-linha (espaços em branco). A extracção de uma cadeia de caracteres termina quando encontra o primeiro espaço em branco depois de ter sido extraído pelo menos um outro caractere qualquer.*/
canal_de_entrada >> uma_string_qualquer
>>um_inteiro_qualquer;
//
Inserção de um inteiro e uma cadeia de caracteres com um espaço entre os dois.canal_de_saida << um_inteiro_qualquer << ' '
<< uma_string_qualquer << endl;
Para que o código acima seja válido, a classe
vector<Item> itens;
...
int numero_de_itens;
canal_de_entrada >> numero_de_itens;
for(int i = 0; i != numero_de_itens; ++i)
itens.push_back(Item(canal_de_entrada));
Item
deve possuir um construtor que extrai a informação necessária
para construir o item do canal de entrada passado como argumento.Se se quiser alterar um Item
previamente construído
através da extracção dos seus dados de um canal, é possível
substituir a última linha do exemplo anterior,
por canal_de_entrada >> itens[i]
ou ainda itens[i].carregaDe(canal_de_entrada)
.
De igual forma, se podem inserir um item num canal usando canal_de_saida
<< itens[i]
e itens[i].guardaEm(canal_de_saida)
.
No entanto, é usual reservar os operadores <<
e >>
para inserções e extracções de valores elementares, por exemplo, os dos
tipos básicos ou de TAD. Nesse caso, reservam-se as operações guardaEm()
e carregaDe()
para efectuar inserções e extracções para classes
propriamente ditas, i.e., que representam objectos e não simples valores.
Novamente, os operadores e operações invocados devem estar definidos para a
classe
Item
. No primeiro caso os operadores istream& operator >>
(istream&, Item&)
e ostream& operator << (ostream&, Item
const&)
e no segundo, as operações void Item::carregaDe(istream&)
e void Item::guardaEm(ostream&) const
.
De seguida apresentam-se alguns exemplos de código típicos da extracção de dados de um canal ligado a um ficheiro.
Exemplo 1:
Exemplo 2:
//
Enquanto se conseguir ler...while(canal_de_entrada >> uma_string_qualquer) {
...
}
Exemplo 3:
char c;
O método operação
/*get()
extrai um caracter de cada vez, incluindo os espaços em branco (espaços, tabuladores e fins-de-linha). Enquanto a operaçãoget()
for bem sucedida e o caracter lido for diferente de fim-de-linha...*/
...adiciona esse caracter a uma cadeia de caracteres.
while(canal_de_entrada.get(c) and c != '\n')
//
uma_string_qualquer += c;
No final do ciclo a cadeia conterá todos os caracteres da linha (incluindo os espaços).
//
Tipicamente, o modo como se insere informação num canal ligado a um ficheiro é diferente do modo como se insere informação num canal ligado ao ecrã.
//
Se tiver falhado a leitura...if(not canal_de_entrada) {
...limpar o estado de erro do canal de entrada ...
//canal_de_entrada.clear();
char c;
...ler e ignorar todos os caracteres encontrados até ao fim da linha.
//
while(canal_de_entrada.get(c) and c != '\n')
;
}
Quando se necessita de inserir a informação de uma classe C++ para um canal de saída é possível definir o operador
<<
cujo segundo argumento é uma referência constante
para uma variável da classe, (por exemplo: ostream& operator
<< (ostream&, Item const&)
) ou definir uma operação que, dado como argumento um canal aberto insere a informação
da classe nesse canal, (por exemplo: void Item::guardaEm(ostream&)
const
). Normalmente, como se referiu, a primeira versão usa-se em
TAD e a segunda em classes propriamente ditas.
No caso das classes propriamente ditas, o formato a usar para apresentar informação
no ecrã e o formato usado para escrever informação
em ficheiros é, normalmente, diferente. Esta razão justifica
por vezes a coexistência de duas operações para inserção em canal.
A primeira, void Item::guardaEm(ostream&)
destina-se a inserir
a informação da classes num formato facilmente legível por um programa,
nomeadamente através da operação complementar void Item::carregaDe(istream&)
,
embora não forçosamente por humanos. A segunda, void Item::mostraEm(ostream&)
destina-se a inserir a informação da classe num canal que estará ligado,
directa ou indirectamente, a um humano, pelo que deverá ser facilmente legível
por humanos, embora não forçosamente por um programa.
No caso dos TAD normalmente os formatos de visualização e de armazenamento
são equivalentes, pelo que nos basta o operador de inserção num canal <<
para os dois efeitos.
A extracção de canais pode realizar-se de uma das seguintes maneiras:
>>
ou um procedimento do tipo void Item::carregaDe(istream&)
.
Este método tem a desvantagem de necessitar de uma instância
da classe (inicializada com lixo, muitas das vezes) para se poder efectuar
a extracção de dados. Como já foi referido, o operador destina-se a TAD
e o método a classes propriamente ditas.void Item::lêDe(istream&)
.ifstream
e ofstream
como argumento
de operações cujos parâmetros são, respectivamente,
istream
e ostream
. O tipo ifstream
representa
apenas canais de entrada de dados associados a ficheiros, mas é
compatível com o tipo
istream
que representa qualquer canal
de entrada de dados (ficheiro, teclado, etc.). Usar istream
nos parâmetros dos métodos torna-os mais flexíveis, pois podem
lidar quer com istream
quer com ifstream
. O
mesmo se passa para canais de saída.Em seguida apresenta-se dois exemplos de utilização
de canais de entrada e saída para guardar e recuperar informação
de um ficheiro. Nestes exemplos são apresentados modos
alternativos de se implementar as operações que permitem
guardar e recuperar informação de uma instância do TAD
Ponto2D
e da classe Aluno
.
Exemplo de classe propriamente dita:...
class Ponto2D {
public:Ponto2D(double const x = 0.0, double const y = 0.0);
double x() const;
double y() const;
private:
double x_;
double y_;
};
...
istream& operator >> (istream& entrada, Ponto2D& ponto)
{
double x, y;
char parenteses_esquerdo, separador, parenteses_direito;Solução um pouco simplista...
//
if(entrada >> parenteses_esquerdo
>> x >> separador >> y
>> parenteses_direito)
if(parenteses_esquerdo == '(' and parenteses_direito == ')' and
separador == ',')
ponto = Ponto2D(x, y);
else
entrada.setstate(ios_base::failbit);
}
return entrada;}
ostream& operator << (ostream& saida, Ponto2D const& ponto){
return saida << '(' << ponto.x() << ',' << ponto.y() << ')';
}
int main()
{
vector<Ponto2D> pontos;
Leitura de pontos de um ficheiro para um vector:
//
ifstream entrada("ficheiro_de_pontos.txt");
int numero_de_pontos;
entrada >> numero_de_pontos;
for(int i = 0; i != numero_de_pontos; ++i) {
Ponto2D ponto;
entrada >> ponto;
pontos.push_back(ponto);
}
if(not entrada) {
Mostrar lista de pontos no ecrã:
cerr << "Leitura falhou!" << endl;
return 1;
}
//
for(vector<Ponto2D>::size_type i = 0; i != pontos.size(); ++i)
Escrita dos pontos guardados no vector num ficheiro:
cout << pontos[i] << endl;
//
ofstream saida("outro_ficheiro_de_pontos.txt");
saida << pontos.size() << endl;
for(vector<Ponto2D>::size_type i = 0; i != pontos.size(); ++i)
saida << alunos[i] << endl;}
...
class Aluno {
public:Aluno(std::string const& nome, std::string const& turma,
int numero);Aluno(istream& entrada);
...
string nome() const;
string turma() const;
int numero() const;
void guardaEm(ostream& saida) const;
void mostraEm(ostream& saida) const;
void carregaDe(istream&);
void lêDe(istream&);
static Aluno novoCarregadoDe(istream& entrada);
private:
std::string nome_;
std::string turma_;
int numero_;
bool cumpreInvariante() const;
};
inline Aluno::Aluno(istream& entrada)
{
assert(entrada);
Pode-se melhorar este código garantindo que cada valor é lido de uma
//
//
linha diferente (actualmente só lê nomes de uma palavra só...). Como?Uma excepção para representar erros de carregamento.
if(not (entrada >> nome_ >> turma_ >> numero_))
throw Excepção(); //
assert(cumpreInvariante());
}
void Aluno::guardaEm(ostream& saida) const
{
assert(saida);
Uma excepção para representar erros de armazenamento.
if(not( saida << nome() << endl
<< turma() << endl
<< numero() << endl))
throw Excepção(); //
}
void Aluno::mostraEm(ostream& saida) const
{
saida << "Nome: " << nome() << endl
<< "Turma: " << turma() << endl
<< "Número: " << numero() << endl;
}
void Aluno::carregaDe(istream& entrada)
{
assert(entrada);
*this = Aluno(entrada);
}
void Aluno::lêDe(istream& entrada)
{
Fica como exercício...
//}
Aluno Aluno::novoCarregadoDe(istream& entrada)
{
assert(entrada);
return Aluno(entrada);
}
...
bool Aluno::cumpreInvariante() const
{
}
int main()
{
vector<Aluno> alunos;
Leitura de alunos de um ficheiro para um vector:
//
ifstream entrada("ficheiro_de_alunos.txt");
ou (ainda que neste caso não seja muito boa ideia porque...
int numero_de_alunos;
entrada >> numero_de_alunos;
for(int i = 0; i != numero_de_alunos; ++i) {
alunos.push_back(Aluno(entrada));
////
...obriga à criação de uma variável aluno com lixo)...// Aluno novo_aluno("", "", 0);
// novo_aluno.carregaDe(entrada);
// alunos.push_back(novo_aluno);
//
ou ainda ...// alunos.push_back(Aluno::novoCarregadoDe(entrada));
}
//
Mostrar lista de alunos no ecrã:
for(vector<Aluno>::size_type i = 0; i != alunos.size(); ++i)
Escrita dos alunos guardados no vector num ficheiro:
alunos[i].mostraEm(cout);
//
ofstream saida("outro_ficheiro_de_alunos.txt");
saida << alunos.size() << endl;
for(vector<Aluno>::size_type i = 0; i != alunos.size(); ++i)
alunos[i].guardaEm(saida);}