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:
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.good())cerr << "O canal de entrada encontra-se num estado "
<<"de erro não podendo ser feitas operações de leitura."
<< endl;
if(canal_de_entrada.fail())cerr << "Uma extracção a partir do canal de entrada falhou." << 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_cadeia_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_cadeia_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_cadeia_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) {...}embora seja preferível:
//Enquanto se conseguir ler...canal_de_entrada >> uma_string_qualquer
while(not canal_de_entrada.fail()) {...canal_de_entrada >> uma_string_qualquer
}ou mesmo:
//Enquanto se conseguir ler...while(true) {
canal_de_entrada >> uma_string_qualquer;
if(canal_de_entrada.fail())
break;...}de modo a evitar guardas de ciclos com efeitos laterais.
Exemplo 3:
/*O método operaçãoget()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...*/
char c;...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).embora seja preferível:
while(true) {
char c;
canal_de_entrada.get(c);
if(canal_de_entrada.fail() or c == '\n')
break;
uma_string_qualquer += c;
}
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(canal_de_entrada.fail()) {...limpar o estado de erro do canal de entrada ...
//canal_de_entrada.clear();...ler e ignorar todos os caracteres encontrados até ao fim da linha.
//char c;
do
canal_de_entrada.get(c);while(not canal_de_entrada.fail() 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(entrada.fail()) {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.good());Pode-se melhorar este código garantindo que cada valor é lido de uma
//
//linha diferente (actualmente só lê nomes de uma palavra só...). Como?
entrada >> nome_ >> turma_ >> numero_;Uma excepção para representar erros de carregamento.
if(entrada.fail())
throw Excepção(); //
assert(cumpreInvariante());
}
void Aluno::guardaEm(ostream& saida) const{assert(cumpreInvariante());
assert(saida.good());
saida << nome() << endl << turma() << endl << numero() << endl;Uma excepção para representar erros de armazenamento.
if(saida.fail())
throw Excepção(); //
}
void Aluno::mostraEm(ostream& saida) const{assert(cumpreInvariante());
assert(saida.good());
saida << "Nome: " << nome() << endl
<< "Turma: " << turma() << endl
<< "Número: " << numero() << endl;
}
void Aluno::carregaDe(istream& entrada){assert(cumpreInvariante());
assert(entrada);
*this = Aluno(entrada);
assert(cumpreInvariante());
}
void Aluno::lêDe(istream& entrada){Fica como exercício...
//}
Aluno Aluno::novoCarregadoDe(istream& entrada){assert(entrada.good());
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);}