Programar em C++/Entrada e saída de dados 2
Entrada/Saída em ficheiros (arquivos)
editarNota introdutória: Este capitulo geralmente é colocado uns capítulos mais para o fim, mas acho por bem que se torne as coisas mais interativas, o que é possível introduzindo agora operações em arquivos, dá muito mais entusiasmo. Encontramos aqui conceitos avançados mas poderão ser deixados para depois se o leitor não quiser observar o tema neste momento.
Gravar (Salvar) os dados para um ficheiro(arquivo)
editarOs dados que mantemos nos programas estão guardados na memória RAM, que é limpa quando o programa ou computador para de funcionar. Isso implicaria que perderíamos toda a informação! Porém existe uma maneira para tornar os dados persistentes que é gravar os dados num ficheiro (arquivo) no "hard drive" (disco rígido) ou no outro meio persistente. Nas formas mais diretas de escrita podemos passar os dados em formato binário para o ficheiro(arquivo). Outros meios avançados para guardar dados podem envolver bases de dados relacionais ou XML.
O que é um ficheiro(arquivo)?
editarUm arquivo é uma coleção de dados que estão localizados numa memória persistente tipo hard drive, cd-rom, etc. Para identificarmos o arquivo podemos atribuir-lhe um nome (filename). Os "filenames" têm usualmente uma extensão, que determina o tipo de arquivo em sistemas operacionais semelhantes aos da Microsoft®, mas que podem ser dispensados em sistemas operacionais que guardam as características dos arquivos no meio de armazenamento, tais quais sistemas UNIX® e seus similares GNU/Linux, FreeBSD, etc... A extensão é representada por 3 ou 4 letras que seguem após o nome do arquivo e um ponto ".". Por exemplo: "joao.doc" ou "joao.odt". Isto diz-me que temos um ficheiro(arquivo) que se chama "joao", e que tem a extensão .doc que refere usualmente a documentos do WORD no primeiro caso e com extensão ".odt" do OpenOffice no segundo. Outros tipos de extensões podem ser ".xls" para documentos EXCEL, ".ods" para planilhas do OpenOffice. ou ainda ".cpp" para ficheiros(arquivos) de códigos de c++.
Ficheiros(Arquivos) binários e tipo texto
editarExistem na verdade dois tipos de ficheiros(arquivos): os do tipo texto e os do tipo binário.
- Os arquivos tipo texto apenas armazenam texto obedecendo uma codificação de caracteres, a mais comum é a ASCII, isto implica o uso do código para armazenamento, ou seja, pode ser que a codificação seja interpretada antes de ser efetivada no meio de armazenamento.
- Os arquivos binários podem guardar mais informação, como imagens, base de dados, programas…Por exemplo, editores de texto com formatação, como o OpenOffice e o Word, guardam os seus arquivos em formatos binários, porque eles possuem além do texto, informação acerca da formatação do texto, para as tabelas, as listas numeradas, tipo de fonte, etc... daí aparecerem os caracteres de formatação tipo ã6, ÌL, h5…
Os arquivos binários poderão ser mais bem explorados em um tópico avançado, vamos trabalhar inicialmente com arquivos tipo texto, que poderemos operar de maneira mais simplificada.
biblioteca padrão fstream
editarAté agora temos usado a biblioteca iostream (i de input + o de output + stream), que suporta, entre várias funcionalidades, o objeto cin para ler da "standard input" (que é usualmente o teclado) e o objeto cout para "standard output" (que usualmente é o monitor)
Ora, agora queremos é ler e escrever para ficheiros(arquivos) e isso requer a biblioteca fstream (f de file + stream). Esta biblioteca define 3 novos tipos de classe:
- ofstream (apenas para saída – "out to a file". serve para criar, manipular ficheiros (arquivos) e escrever, não serve para ler).
- ifstream (apenas para entrada – "in from a file" . serve para ler ficheiros (arquivos), receber dados dos mesmos, não serve para criar nem escrever).
- fstream (este conjuga os dois tipos anteriores, "input and output to file". cria ficheiros (arquivos), escreve e lê informação dos mesmos.
Abrir um ficheiro(arquivo)
editarUm ficheiro(arquivo) deve ser aberto pelo programa para que o mesmo possa ser manipulado, a abertura do arquivo implica, entre outras coisas, atribuir um identificador que nos permita ter acesso aos seus dados. É necessário criar uma linha de comunicação entre o arquivo e o objeto stream.
Podemos recorrer a dois métodos para abrir um ficheiro (arquivo):
- Usando um construtor;
- Usando a função membro chamada de "open".
Usando o Construtor
editarO construtor é uma função que é automaticamente chamada quando tentamos criar uma instância de um objeto.
fstream afile; //é criado uma instância do fstream chamada de afile
Os construtores de objetos podem ser sobrecarregados, ou seja, para a mesma classe podemos ter um construtor sem argumentos, com um argumento, dois argumentos, etc. No exemplo anterior criamos um sem argumentos. Os construtores não retornam valores, geralmente o compilador reporta erro quando se declara funções que retornam valor e estas têm o mesmo nome da classe, pois este nome é reservado para os construtores. Vamos dar um exemplo com dois argumento:
ofstream outfile ("joao.doc", ios::out);
Chama o construtor com dois argumentos, criando uma instância de ofstream e abrindo o ficheiro(arquivo) "joao.doc" para operações de saída.
Usando a função membro "open"
editarEsta função tem como primeiro argumento o nome e localização do ficheiro/(arquivo) a ser aberto, o segundo argumento especifica o modo de abertura.
Sobre a questão da localização existem 2 tipos, o "path" relativo e o "path" absoluto. Para este último indicamos o caminho todo: "c:\\....\\joao.doc" em sistemas Microsoft® ou "/home/joao/joao.odt" para sistemas UNIX® e similares. O "path" relativo dispensa essa descrição se o ficheiro/(arquivo) estiver (na mesma directoria)/(no mesmo diretório) que o programa.
Sobre a questão do modo de abertura temos as seguintes modalidades:
Modo do abertura | sinalizador (Flag) |
Descrição |
---|---|---|
ios::app | "Append mode" | Todos os dados do arquivo são preservados e qualquer saída é escrita a partir do fim do arquivo. |
ios::ate | Se o arquivo já existe,o programa vai diretamente ao seu fim.O modo de escrita é então feito de forma aleatória.Usado normalmente com arquivos do modo binário(binary mode). | |
ios::binary | "Binary mode" | Informações são escritas na forma binária e não na forma textual(text mode). |
ios::in | "Input mode" | Leitura de informações de arquivo(não irá criar um arquivo novo) |
ios::out | "Output mode" | Informações serão escritas no arquivo. |
ios::trunc | Se o arquivo já existe,suas informações serão truncadas, outra forma de se dizer: deletadas e reescritas. |
Os sinalizadores (flags) são números em potências da base binária, portanto podemos ter vários flags ao mesmo tempo se usarmos o operador unário para a operação "OU", como no exemplo abaixo:
ofstream outfile; //crio o objeto outfile
outfile.open("students.dat", ios::binary | ios::app); /*chamo a função membro open do objeto,
com o 1º parâmetro que é o nome do arquivo
e o 2º o modo de abertura. */
Observe que estamos abrindo o arquivo "students.dat" em modo binário e ao mesmo tempo com o modo "append", isto significa que abriremos o arquivo e poderemos preservar o seu conteúdo anterior inserindo os novos dados no fim do arquivo.
Comparando os dois métodos (pela função membro e pelo construtor)
editarO primeiro método é similar a ter
int age;
age=39;
O segundo método é similar a
int age=39;
A escolha do melhor método em cada situação depende do contexto em que estamos criando o código, geralmente quando já temos o objeto criado e ele está fechado podemos abrir um novo arquivo com ele e depois fechá-lo novamente, isto nos sugere que usemos a função open quando o objeto deve abrir arquivos diferentes em cada trecho de código, embora que possam surgir outras funcionalidades, dependendo de como o projeto foi idealizado.
Abrir um arquivo para leitura
editarA história aqui é a mesma só tem uma diferença: é que no caso de leitura, não será criado nenhum ficheiro (arquivo) caso ele não exista.
ifstream arq; //cria objeto "arq"
arq.open (“joão.doc”); //chama função membro open ao objeto "arq", com o
//parâmetro do nome do ficheiro
Poderíamos fazer o mesmo com o construtor:
ifstream arq (“joão.doc”);
Ou ainda
fstream bloco;
bloco.open("joao.doc", ios::in)
ou ainda
fstream b(“joao.doc”, ios::in)
Há mais uma nota a fazer, se quisermos ler e escrever, não podemos usar o ofstream e o ifstream ao mesmo tempo, teremos de usar o fstream. Teremos de fazer:
fstream a (“joão.doc”, ios::in | ios::out);
Neste caso, o comportamento padrão é preservar o conteúdo do ficheiro (arquivo) ou criá-lo caso ele não exista.
Verificar se o ficheiro (arquivo) foi aberto.
editarVamos verificar o que acontece quando tentamos abrir um arquivo que não existe, a primeira versão do nosso exemplo observa o comportamento básico do fstream:
#include <fstream>
#include <iostream>
using namespace std;
int main ()
{
ifstream arq; //crio objeto "arq" da classe ifstream - leitura
arq.open("joao.doc"); //chamo função membro open
cout << "(arq) = " << arq << endl; //imprime o objeto
cout << "(arq.fail()) = " << arq.fail() << endl; //chamo função membro fail
#ifdef WIN32
system ("pause");
#endif
return 0;
}
No caso do ficheiro (arquivo) “joao.doc” não existir:
(arq) = 00000000 (arq.fail()) = 1
No caso do ficheiro (arquivo) “joao.doc” existir no mesmo diretório que o programa:
(a) = 0012FE40 (a.fail()) = 0
Repare que o resultado é a impressão do endereço, do objeto a de ifstream. dá um ponteiro!!
Agora, vajamos um exemplo mais completo:
#include <fstream>
#include <iostream>
using namespace std;
int main ()
{
ifstream arq; //crio objeto "arq" da classe ifstream - leitura
string str;
arq.open("joao.doc"); //chamo função membro open
if (arq.is_open() && arq.good())
{
arq >> str;
cout << "conteúdo: \n " << str << endl; //imprime o conteúdo do arquivo
arq.close();
}
#ifdef WIN32
system ("pause");
#endif
return 0;
}
Observe que aqui verificamos se o arquivo foi aberto com a função membro is_open() que retorna verdadeiro "true" caso o arquivo foi aberto, depois verificamos se o arquivo foi aberto satisfatoriamente através da função membro good(), que também retorna verdadeiro se o arquivo pode ser usado.
Fechar um ficheiro (arquivo)
editarDevemos fechar depois de ler e/ou escrever. Mas por que, se o objeto do ficheiro irá ser fechado assim que o programa acabar? Porque estamos a utilizar recursos com um ficheiro (arquivo) aberto, porque alguns sistemas operativos (operacionais) limitam o nº de ficheiros (arquivos) abertos, e estando este aberto impede que outros se possam abrir e por fim porque se não fecharmos, outros programas não poderão abri-lo até que o fechemos. Este comportamento faz parte de um esquema de controle de acesso usado pelo sistema para assegurar que os arquivos não serão usados por processos diferentes ao mesmo tempo.
ofstream outfile;
outfile.open("students.dat");
// ....
outfile.close();
Vamos criar um exemplo mais real. Queremos criar um programa que escreva informação inserida pelo utilizador num ficheiro por nós escolhido
#include <fstream>
#include <iostream>
using namespace std;
int main ()
{
char data[80]; //criamos um array de 80 caracteres
ofstream outfile; //criamos objeto da classe ofstream
outfile.open("joao.doc"); //chamamos a função membro da classe para o objeto criado.
// Esta função membro cria o arquivo "joao.doc"
if (outfile.is_open() && outfile.good()) //verificamos se está tudo bem
{ cout << "digite o seu nome: "; //imprime no ecrã (monitor) a frase
cin.getline(data, 80); //chama função membro getline do objeto cin para
//ler o que foi escrito pelo usuário
outfile << data << endl; //coloca o array no objeto criado.
outfile.close(); //fechamos o objeto.
}
#ifdef WIN32
system ("pause");
#endif
return 0;
}
Podemos ir ver o novo ficheiro/arquivo com o nome joao.doc e tem lá escrito aquilo que digitamos.
Agora vamos tentar ler o que escrevemos no documento criado.
#include <fstream>
#include <iostream>
using namespace std;
int main ()
{
char data[80];
ifstream infile;
infile.open("joao.doc");
if (infile.is_open() && infile.good()) //verificamos se está tudo bem
{ infile >> data; //colocamos os dados abertos no array
cout << data << endl;
infile.close();
}
#ifdef WIN32
system ("pause");
#endif
return 0;
}
Repare que se tivéssemos escrito duas palavras, apenas uma era apresentada (ela pára no primeiro whitespace), para isso necessitaríamos de repetir:
#include <fstream>
#include <iostream>
using namespace std;
int main ()
{
char data[80];
ifstream infile;
infile.open("joao.doc");
if (infile.is_open() && infile.good()) //verificamos se está tudo bem
{ infile >> data; //colocamos os dados abertos no array
cout << data << endl;
infile >> data; //colocamos os dados abertos no array
cout << data << endl;
infile.close();
}
#ifdef WIN32
system ("pause");
#endif
return 0;
}
Agora já obtemos 2 palavras e são apresentadas em linhas diferentes. Mas temos de arranjar um método para não estar a repetir constantemente, podemos fazer isso com
infile.getline(data, 80);
Então ficamos com:
#include <fstream>
#include <iostream>
#include <string>
using namespace std;
int main ()
{
string data;
ofstream outfile;
outfile.open("joao.doc");
if (outfile.is_open() && outfile.good()) //verificamos se está tudo bem
{
cout << "Writing to the file" << endl;
cout << "===================" << endl;
cout << "Enter class name: ";
getline(cin, data);
outfile << data << endl;
cout << "Enter number of students: ";
cin >> data;
cin.ignore(); //esta função membro é para limpar o caractere
//newline do inputbuffer depois de usar o objeto
//cin com o operador de extração >>
outfile << data<< endl;
outfile.close();
}
ifstream infile;
infile.open("joao.doc ");
if (infile.is_open() && infile.good()) //verificamos se está tudo bem
{ cout << "Reading from the file" << endl;
cout << "=====================" << endl;
getline(infile, data);
cout << data << endl;
getline(infile, data);
cout << data << endl;
infile.close();
}
#ifdef WIN32
system ("pause");
#endif
return 0;
}
Looping pelo ficheiro (arquivo).
editarE se não soubermos quantas linhas tem o arquivo? O objeto ifstream tem uma função membro que é a eof() (e-end+o-of+f-file). Esta função não tem parâmetros e retorna "true" se o fim do arquivo for alcançado e "false" caso contrário. No entanto, esta função eof() não é de confiança com os ficheiros (arquivos) texto como o é para os binários (é que nos ficheiros (arquivos) binários não existem espaços em branco).
A melhor alternativa é usar a função membro fail().
ifstream infile;
infile.open("joao.doc");
if (infile.is_open() && infile.good())
{ infile >> data;
while(!infile.fail())
{ infile >> data;
cout << data;
}
infile.close();
}
Refazendo tudo
#include <fstream>
#include <iostream>
#include <string>
using namespace std;
int main ()
{
string data;
ofstream outfile;
outfile.open("joao.doc");
if (outfile.is_open() && outfile.good())
{
cout << "Escrevendo no arquivo" << endl;
cout << "===================" << endl;
cout << "Informe o nome da classe: ";
getline(cin, data);
outfile << data<< endl;
cout << "informe o número de estudantes: ";
cin >> data;
cin.ignore();
outfile << data<< endl;
outfile.close();
}
ifstream infile;
infile.open("joao.doc");
if (infile.is_open() && infile.good())
{
cout << "Lendo do arquivo" << endl;
cout << "=====================" << endl;
getline(infile, data);
while(!infile.fail())
{
cout << data << endl;
getline(infile, data);
}
infile.close();
}
#ifdef WIN32
system ("pause");
#endif
return 0;
}
Agora vamos fazer o nosso programa mais modular:
- . writeFile – para abrir um arquivo para escrita usando o ofstream e
- . readFile - ler do ficheiro (arquivo) usando o ifstream
- . Cada função irá verificar se o ficheiro (arquivo) foi aberto com sucesso
#include <fstream>
#include <iostream>
#include <string>
using namespace std;
bool writeFile (ofstream&, char*);
bool readFile (ifstream&, char*);
int main ()
{
string data;
bool status;
ofstream outfile;
status = writeFile(outfile, "students.dat");
if (!status)
{
cout << "Arquivo não pode ser aberto para escrita.\n";
cout << "Programa terminando...\n";
return 0;
}
else
{
cout << "Escrevendo no arquivo" << endl;
cout << "===================" << endl;
cout << "Informe o nome da classe: ";
getline(cin, data);
outfile << data<< endl;
cout << "Informe o número de estudantes: ";
cin >> data;
cin.ignore();
outfile << data<< endl;
outfile.close();
}
ifstream infile;
status = readFile(infile, "students.dat");
if (!status)
{
cout << "O arquivo não pode ser aberto para leitura.\n";
cout << "Programa terminando...\n";
return 0;
}
else
{
cout << "Lendo do arquivo" << endl;
cout << "=====================" << endl;
getline(infile, data);
while(!infile.fail())
{
cout << data << endl;
getline(infile, data);
}
infile.close();
}
#ifdef WIN32
system ("pause");
#endif
return 0;
}
bool writeFile (ofstream& file, char* strFile)
{
file.open(strFile);
return !(file.fail()||!file.is_open()||!file.good());
}
bool readFile (ifstream& ifile, char* strFile)
{
ifile.open(strFile);
return !(ifile.fail()||!ifile.is_open()||!ifile.good());
}
Manipuladores
editarOs objetos das classes "stream" podem ser configurados para fornecer e reportar os dados de maneira pré-formatada. Da mesma maneira que temos a formatação quando usamos funções de formatação, como printf() e scanf(), na linguagem C, podemos usar os manipuladores na linguagem C++ para informar os objetos streams em que formato desejamos receber os dados deles ou fornecer para eles.
Abaixo temos uma série de manipuladores úteis:
Manipulator | Uso |
---|---|
boolalpha | Faz com que variáveis tipo bool sejam reportadas como "true" ou "false". |
noboolalhpa (padrão) | Faz com que variáveis tipo bool sejam reportadas omo 0 ou 1. |
dec (padrão) | Determina que variáveis tipo inteiras (int) sejam reportadas na base 10. |
hex | Determina que variáveis tipo inteiras (int) sejam reportadas em hexadecimal. |
oct | Determina que variáveis tipo inteiras (int) sejam reportadas em octal. |
left | Faz com que textos sejam justificados a esquerda no campo de saída. |
right | Faz com que textos sejam justificados a direita no campo de saída. |
internal | Faz com que o sinal de um número seja justificado a esquerda e o número seja justificado a direita. |
noshowbase (padrão) | Desativa a exibição do prefixo que indica a base do número. |
showbase | Ativa a exibição do prefixo que indica a base do número. |
noshowpoint (padrão) | Mostra o ponto decimal apenas se uma parte fracionária existe. |
showpoint | Mostra o ponto decimal sempre. |
noshowpos (padrão) | Nenhum sinal "+" prefixado em números positivos. |
showpos | Mostra um sinal "+" prefixado em números positivos. |
skipws (padrão) | Faz com que espaços em branco, tabulações, novas linhas "\n" sejam descartados pelo operador de entrada >>. |
noskipws | Faz com que espaços em branco, tabulações, novas linhas "\n" não sejam descartados pelo operador de entrada >> |
fixed (padrão) | Faz com que números com ponto flutuante sejam mostrados em notação fixa. |
Scientific | Faz com que números com ponto flutuante sejam mostrados em notação científica. |
nouppercase (padrão) | 0x é mostrado para números em hexadecimal e para notação científica. |
uppercase | 0X é mostrado para números em hexadecimal e para notação científica. |
Ajustando a largura da entrada/saída
editar- setw(w) - Ajusta a largura da saída e entrada para w; precisa ser incluído.
- width(w) - Uma função membro das classes iostream.
Preenchimento de espaços em branco
editar- setfill(ch) - Preenche os espaços em branco em campos de saída com ch; precisa ser incluído.
- fill(ch) - Uma função membro das classes iostream.
Ajustando a precisão
editar- setprecision(n) - Ajusta a precisão de casas decimais em números com ponto flutuante, para n dígitos. Este ajuste é apenas visual, de forma que o manipulador não afeta o modo de cálculo do número pelo programa.
Exemplificando o uso de manipuladores:
#include <iostream>
#include <iomanip>
#include <string>
using namespace std;
int main()
{
int intValue = 15;
cout << "Número inteiro" << endl;
cout << "Padrão: " << intValue << endl;
cout << "Octal: " << oct << intValue << endl;
cout << "Hexadecimal: " << hex << intValue << endl;
cout << "Ativando showbase " << showbase << endl;
cout << "Decimal: " << dec << intValue << endl;
cout << "Octal: " << oct << intValue << endl;
cout << "Hexadecimal: " << hex << intValue << endl;
cout << "Desativando showbase " << noshowbase << endl;
cout << endl;
double doubleVal = 12.345678;
cout << "Números com ponto flutuante" << endl;
cout << "Padrão: " << doubleVal << endl;
cout << setprecision(10);
cout << "Precisão de 10: " << doubleVal << endl;
cout << scientific << "Notação científica: " << doubleVal << endl;
cout << uppercase;
cout << "Caixa alta: " << doubleVal << endl;
cout << endl;
bool theBool = true;
cout << "Booleano" << endl;
cout << "Padrão: " << theBool << endl;
cout << boolalpha << "BoolAlpha ativo: " << theBool << endl;
cout << endl;
string myName = "John";
cout << "Strings" << endl;
cout << "Padrão: " << myName << endl;
cout << setw(35) << right << "Com setw(35) e \"right\": " << myName << endl;
cout.width(20);
cout << "Com width(20): " << myName << endl;
cout << endl;
#ifdef WIN32
system ("pause");
#endif
return 0;
}
Exercícios
editar- Quero colocar num documento, uma lista das combinações possíveis entre a,b,c e d. com a respectiva ordenação e quantidade;
- . Quero que seja a pessoa a escolher o nome do ficheiro (arquivo) e escrever também a localização;
- . Quero que seja depois tansformado num sistema modular;
- . Encontrar uma maneira para contar o nº de espaços em branco, o nº de caracteres "." que quisermos de um dado documento.
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
int blank_count = 0;
int char_count = 0;
int sentence_count = 0;
char ch;
ifstream object("jo.txt");
if (! object)
{
cout << "Erro abrindo arquivo." << endl;
return -1;
}
while (object.get(ch))
{
switch (ch)
{
case ' ':
blank_count++;
break;
case '\n':
case '\t':
break;
case '.':
sentence_count++;
break;
default:
char_count++;
break;
}
}
cout << "Existem " << blank_count << " espaços em branco;" << endl;
cout << "Existem " << char_count << " caracteres;" << endl;
cout << "Existem " << sentence_count << " sentenças." << endl;
#ifdef WIN32
system ("pause");
#endif
return 0;
}