AULA 15 - Programação II - Graduação
Fluxos
C++ oferece diversos recursos para acessar arquivos, ou mais genericamente streams.
O nome stream representa um fluxo de entrada ou de saída, e pode ser entendido de diversas formas.
Por exemplo, cout é um fluxo de saída e cin é um fluxo de entrada, ambos associados com o terminal.
Da mesma forma, pode-se usar fluxos de saida para strings, por exemplo, que permitem compor strings como se estivéssemos escrevendo na tela.
Um fluxo, ou stream, é uma abstração representando a transmissão de dados entre:
- um emissor, aquele que produz informação;
- um receptor, aquele que consome a informação produzida.
Em C++, as instruções de E/S não fazem parte da linguagem. Elas são oferecidas em uma biblioteca padrão, que implementa os fluxos a partir de uma hierarquia de classes.
Por default, cada programa C++ pode utilizar três fluxos:
- cout: que corresponde à saída padrão.
- cin: que corresponde à entrada padrão.
- cerr: que corresponde à saída padrão de erros.
Para utilizar outros fluxos, devemos criá-los e associá-los a arquivos, normais ou especiais (dispositivos), ou então a vetores de caracteres.
Classes de Fluxo
As classes declaradas em iostream.h permitem a manipulação dos periféricos padrões, como por exemplo vídeo e teclado:
- ios: classe base de E/S em fluxos; ela contém um objeto da classe streambuf para a
gestão dos buffers de entrada e saída.
- istream : classe derivada de ios para os fluxos de entrada.
- ostream : classe derivada de ios para os fluxos de saída.
- iostream : classe derivada de istream e de ostream para os fluxos bidirecionais.
- istream_withassign, ostream_withassign e iostream_withassign: classes derivadas respectivamente de istream, ostream e iostream, que oferecem o operador de atribuição.
Os fluxos padrões cin, cout e cerr são instâncias, objetos dessas classes.
As classes declaradas em fstream.h permitem a manipulação de arquivos em disco:
- fstreambase : classe base para as classes derivadas ifstream, ofstream e fstream. Ela
mesma é derivada de ios e contém um objeto da classe filebuf.
- ifstream : classe que permite realizar entrada a partir de arquivos.
- ofstream : classe que permite efetuar saída em arquivos.
- fstream : classe que permite realizar entrada/saída em arquivos.
As classes declaradas em strstream.h permitem simular operações de entrada e saída a partir de buffers na memória principal.
Elas operam da mesma maneira que as funções sprintf() e sscanf() da linguagem C.
- strtreambase : classe base para as classes seguinte. Ela contém um objeto da classe
strstreambuf (derivada de streambuf).
- istrstream : derivada de strtreambase e de istream, permite leitura a partir de um
buffer, da mesma maneira que a função sscanf().
- ostrstream : derivada de strtreambase e de ostream, permite escrita em buffer, do
mesmo modo que a função sprintf().
- strstream : derivada de istrstream e de iostream, permite leitura e escrita em buffer.
O fluxo de saída padrão
Objeto "cout'"
O objeto cout representa um objeto da classe ostream, ou seja, o stream de saída no C++.
Este stream é uma espécie de seqüência (fluxo) de dados a serem impressos na tela.
Para realizar a impressão, usa-se o "operador de inserção" que "insere" dados dentro do stream.
<< Operador de Inserção
O operador << sobrecarregado executa a saída (imprime na tela) com streams em C++.
O objeto cout é usado em conjunto com ele para a impressão de dados.
#include <iostream>
using namespace std;
int main()
{
cout << "Imprimindo o famoso HELLO WORLD!!!\n";
// imprimindo uma linha usando múltiplos comandos
cout << "Teste com ";
cout << "dois couts\n";
// usando o manipulador endl
// endl gera um caractere nova linha, e também descarrega o buffer de saída
cout << "Escrevendo uma linha..." << endl;
cout << "Mais uma vez...\n";
cout << flush; // agora apenas esvaziando o buffer de saída, sem gerar nova linha
return 0;
}
<< sempre retorna uma referência ao objeto cout.
Desta forma, é possível encadear vários cout, permitindo uma combinação de constantes e variáveis, como mostra o exemplo abaixo:
int a = 10; int b = 12; // imprime uma string e o resultado da soma entre as variáveis a e b cout << "Encadeando saidas: Somandos dois numeros " << a + b << endl;
A saída de caracteres também pode ser feita sem o uso do operador de inserção <<.
Para isto, usa-se a função membro put, que retorna uma referência para o objeto cout.
Exemplos:
char c = 'x';
cout.put('t'); // fazendo a saída de um único caractere
cout.put(' ').put(c).put('\n'); // fazendo a saída encadeada
O fluxo de saída padrão ostream permite:
- saída formatada e não formatada (em um streambuf)
- sobrecarga do operador de inserção <<.
- exibir tipos predefinidos da linguagem C++.
- exibir tipos definidos pelo programador.
Além do operador de inserção (<<), a classe ostream possui os seguintes métodos:
| ostream & put(char c) | insere um caracter num fluxo |
|---|---|
| ostream & write(const char *, int n) | insere n caracteres num fluxo |
| streampos tellp() | retorna a posição corrente dentro do fluxo |
| ostream & seekp(streampos n) | posiciona-se a n bytes a partir do início
do arquivo. O tipo streampos corresponde à uma posição do arquivo, que inicia-se em 0. |
| ostream & seekp(streamoff d, seek_dir r) | Posiciona-se a d bytes a partir do início
do arquivo (r = beg), a posição corrente (r = cur) ou o final do fluxo (r = end) |
| ostream & flush() | esvazia o buffer do fluxo |
Exemplo:
#include <ostream.h>
void main(void)
{
cout.write("Hello world!", 12);
cout.put('\n');
}
O fluxo de entrada padrão
Objeto "cin"
O objeto cin representa um objeto da classe istream, isto é, é o stream de entrada padrão no C++.
Ele realiza a leitura de um seqüência de dados, sem espaços e sem tabulações, vindas do teclado.
Para coletar estes dados armazenados, usa-se o "operador de extração" que "extrai" dados do stream.
>> Operador de Extração
O operador >> sobrecarregado executa a entrada com streams em C++, usando o comando cin para aquisição de dados.
Variáveis podem ser usadas para o armazenamento das informações.
#include <iostream>
using namespace std;
int main()
{
int Num1;
int Num2;
cout << "Lendo o primeiro numero...: ";
cin >> Num1;
cout << endl;
cout << "Lendo o segundo numero....: ";
cin >> Num2;
cout << endl;
return 0;
}
Da mesma forma que o operador de inserção, >> sempre retorna uma referência ao objeto cin, permitindo o encadeamento de vários cin.
Exemplo:
float c;
float d;
cin >> c >> d; // entra com um float, pressiona <ENTER>, entra com outro
// float, pressiona <ENTER>
Outras peculiaridades usando o operador de extração:
while(cin >> dado) // repete enquanto o usuário não digitar fim de
// arq (Ctrl+Z ou Ctrl+D)
cin.eof(); // devolve 1 (verdadeiro) se chegou ao fim do arquivo
#include <iostream> // std::cin, std::cout
#include <string> // std::string
using namespace std;
int main () {
cout << "Por favor, entre uma palavra ou numero: ";
char c = cin.get();
if ( (c >= '0') && (c <= '9') )
{
int n;
cin.putback (c);
cin >> n;
cout << "Voce digitou um numero: " << n << '\n';
}
else
{
string str;
cin.putback (c);
getline (cin,str);
cout << "Voce digitou uma palavra: " << str << '\n';
}
return 0;
}
O fluxo de entrada padrão, istream, permite:
- entrada formatada e não formatada (em um streambuf)
- sobrecarga do operador de extração >>.
- entrada de tipos predefinidos da linguagem C++.
- entrada de tipos definidos pelo programador.
O próximo exemplo mostra a sobrecarga do operador >> para a classe Complexo:
Exemplo:
class Complexo {
friend istream & operator>>(istream &in, Complexo &x);
...
};
Assim como no caso de inserção, para que o operador de extração possa ser usado de maneira encadeada, a função operator>> também deve retornar uma referência ao objeto istream a partir do qual ela foi chamada.
Exemplo:
istream operator>> istream & operator>>(istream &in, Complexo &x)
{
in >> x.r;
char sinal;
in >> sinal;
assert( sinal=='+' || sinal=='-' );
in >> x.i;
if( sinal=='-' ) x.i *= -1;
char i;
in >> i;
if( i!='i' ) x.i=0;
return in;
}
void main(void)
{
Complexo x;
cout << "Digite um número complexo: ";
cin >> x;
...
}
Além do operador de extração, a classe istream oferece os seguintes métodos:
| int get() | extrai e retorna um caracter (ou EOF) |
|---|---|
| istream & get(char &c) | extrai e armazena em c um caracter |
| int peek() | informa o próximo caracter, sem extraí-lo |
| istream & get(char *ch, int n, char d='\n') | extrai n-1 caracteres do fluxo e os armazena a partir do endereço ch, parando assim que o delimitador é encontrado. |
| Istream & getline(char *ch, int n, char d='\n') | como o método anterior, exceto que o delimitador é extraído e descartado |
| istream & read(char *ch, int n) | extrai um bloco de pelo menos n bytes do fluxo e os armazena a partir do endereço ch. O número de bytes efetivamente lidos é obtido através do método gcount() |
| int gcount() | retorna o número de caracteres extraídos na última leitura |
| streampos tellg() | retorna a posição corrente dentro do fluxo |
| istream & seekg(streampos n) | posiciona-se a n bytes a partir do início do arquivo. O tipo streampos corresponde à uma posição do arquivo, iniciando em 0. |
| istream & seekg(streamoff d, seek_dir r) | Posiciona-se a d bytes a partir do início do arquivo (r = beg), a posição corrente (r = cur) ou o final do fluxo (r = end) |
| istream & flush() | esvazia o buffer do fluxo |
Manipuladores de streams
Executam tarefas de formatação, por meio de diferentes recursos.
Alguns deles estão abaixo destacados:
Base do stream de inteiros
#include <iomanip>
#include <iostream>
using namespace std;
main()
{ int n;
cout<< "Entre com um valor inteiro: ";
cin >> n;
cout << hex << n << endl // apresenta n em hexadecimal
<< oct << n << endl // apresenta n em octadecimal
<< setbase(10) << n << endl; // apresenta n na base 10
return 0;
}
Precisão em ponto flutuante
double PI = 3.14159265;
cout.precision(5); // define a precisão cout << setiosflags (ios::fixed) << f << endl; cout << PI; // imprime PI com 5 casas decimais
ou
cout << setiosflags (ios::fixed) << setprecision(5) << PI; // define e imprime PI com 5 casas decimais
Para retornar ao formato de impressão padrão:
cout << setiosflags (ios::scientific); cout << f << endl;
Largura de campo
Para definir a largura da impressão de uma variável usa-se o setw(n), onde n é o número de caracteres desejado para a impressão desta variável.
Note que o setw só afeta a próxima variáveldo cout.
int caixas = 45; cout << "Número de caixas: " << setw(10) << caixas << endl;
Alinhamento da impressão
É possível alinhar os dados impressos tanto à esquerda quanto à direita, com os manipuladores left e right.
cout << right << 10; cout << left << 10;
Entrada de Strings
Em determinadas ocasiões, deseja-se coletar dados que contenham strings com tabulações, espaços em branco e/ou novas linhas.
Frases são exemplos onde este tipo de situação ocorre.
No entanto, o operador >> sobrecarregado ignora tais caracteres.
Para englobar essas situações, C++ oferece o uso da função-membro getline().
Esta função remove o delimitador do stream (isto é, lê o caractere e o descarta) e armazena o mesmo em uma variável definida pelo usuário.
Abaixo, um exemplo de sua utilização.
#include <iostream>
using namespace std;
int main()
{
const TAM = 80;
char guarda[TAM];
cout << "Digite uma frase com espaços: " << endl;
cin.getline(guarda, TAM);
cout << "A frase digitada é: \n" << guarda << endl;
return 0;
}
Exemplo 2 - Entrada de string com cin:
//*********************************
// cin.cpp
//
// Entrada de string com CIN
//
//*********************************
#include <iostream>
#include <stdlib.h>
using namespace std;
using std::cout;
using std::endl;
int main(int argc, char *argv[])
{
string s1, s2;
int i,j;
cout << "Ola'!" << endl;
cin >> s1;
cout << "String lida 1:" << s1 << "*" << endl;
cin >> s2;
cout << "String lida 2:" << s2 << "*" << endl;
getline(cin, s1); // pega o enter que ficou no buffer
getline(cin, s1); // permite a leitura de uma string com espaços em branco
cout << "String lida 3:" << s1 << "*" << endl;
system("PAUSE");
return 0;
}
SAÍDA:
/* Ola'! um teste String lida 1:um* String lida 2:teste* outro teste String lida 3:outro teste* Press any key to continue . . . */
Exemplo 3 - Entrada de string com getline:
//*********************************
// getline.cpp
//
// Entrada de string com getline
//
//*********************************
#include <iostream>
#include <stdlib.h>
using namespace std;
using std::cout;
using std::endl;
int main(int argc, char *argv[])
{
string s1, s2;
int i,j;
cout << "Ola'!" << endl;
getline(cin, s1);
cout << "String lida 1:" << s1 << "*" << endl;
getline(cin, s2);
cout << "String lida 2:" << s2 << "*" << endl;
getline(cin, s1, '&'); // para a leitura no '&'
// note que o caracter '&' NÃO vai para 's1'
cout << "String lida 1:" << s1 << "*" << endl;
system("PAUSE");
return 0;
}
SAÍDA:
/* Ola'! um teste String lida 1:um teste* outro teste String lida 2:outro teste* mais um & teste String lida 1:mais um * Press any key to continue . . . */
String Streams
String stream é um recurso que permite escrevermos em uma string como se estivéssemos realizando saída na tela (ex: via cout).
Dessa forma, é possível utilizar todas as funcionalidades da iostream (formatos, alinhamento, precisão numérica, tamanho de campo, etc) em uma string.
É útil, por exemplo, para retornarmos informações sobre um objeto sob forma de string, especialmente quando é preciso combinar dados numéricos com dados não-numéricos.
Para utilizar o recurso de string stream, é necessário primeiramente incluir o header sstream.
A partir daí, deve-se declarar um objeto do tipo ostringstream (output string stream) e utilizá-lo como se fosse uma saída padrão, ou seja, da mesma forma que cout:
Finalmente, para se obter a string armazenada no objeto ostringstream usa-se o método str:
#include <iostream>
#include <iomanip>
#include <cmath>
#include <sstream>
using namespace std;
string converte(float valor)
{
ostringstream aux; // Declara o string stream de saída chamado aux
aux << "Raiz do valor digitado (Exemplo de saída em string) "; // Escreve em aux
aux << fixed << setprecision(2) << sqrt(valor); // idem
return aux.str(); // Retorna a string resultante a partir do stream aux
}
int main()
{
float v;
cout << "Digite o valor: ";
cin >> v;
cout << converte(v);
}
Note que se o objetivo for apenas concatenar (agrupar) duas ou mais strings, não é necessário usar string stream: basta empregar o operador de concatenação (+).
É muito comum a criação de um método denominado toString, que tem o papel de retornar uma representação do objeto armazenado como uma string.
Por exemplo:
#include <iostream>
#include <sstream>
#include <string>
class Pessoa
{
private:
string nome;
string CPF;
int idade;
public:
Pessoa(string n, string cpf, int i) {
...
}
...
string toString();
...
};
...
string Pessoa::toString() {
// Cria um fluxo de saída em uma string
ostringstream aux;
// "Escreve" os dados no fluxo
aux << nome << " - " << CPF << " - " << idade;
// Retorna string contida no fluxo
return aux.str();
}
Porém, o mais usual é entender-se fluxos como arquivos físicos, como veremos a seguir.
Streams de Dados (Arquivos)
Quando consideramos streams como arquivos físicos, a sua manipulação se resume em duas etapas:
- A gravação dos dados ou criação do arquivo;
- A leitura dos dados
Gravando em um Stream
Para exemplificar a gravação de dados, criaremos um programa que grava um conjunto de números sorteados em um arquivo texto.
O primeiro passo é incluir o header fstream, que contém as classes para manipulação de arquivos físicos (file streams).
Repare que o programa-exemplo a seguir inclui mais headers, para que seja possível, por exemplo, sortear os números.
#include <fstream> // para usar file streams (ifstream,ofstream) #include <iostream> // para usar cin,cout #include <string> // para usar string #include <iomanip> // para usar manipuladores (setw, right, ...) #include <cstdlib> // para usar srand(), rand() e exit() #include <time.h> // para usar time() using namespace std;
A seguir, cria-se uma instância de ofstream, ou seja, um "objeto" arquivo de saída.
int main() {
// Cria output file stream (ofstream)
ofstream arqsaida;
O próximo passo é abrir o arquivo, usando o modo ios::out, que cria o arquivo e abre para escrita.
Cuidado: se esse modo for usado em arquivos já existentes, eles serão apagados!
O método is_open() retorna false se o arquivo não está aberto, então usamos para testar se foi possível realizar a operação.
// Cria e abre arquivo
arqsaida.open( "teste.txt" , ios::out );
// Se houver erro, sai do programa
if (!arqsaida.is_open())
return 0;
Neste ponto, chamamos a função srand(), usando o resultado da função time() como parâmetro: esta última retorna o tempo atual, e srand é utilizada para incializar o gerador de números aleatórios (o que chamamos de semente).
Como se emprega o tempo atual do sistema, o gerador sempre será inicializado com uma semente diferente, garantindo números "mais" aleatórios.
// Gera a semente aleatória srand(time(0));
Agora podemos começar a gravar os dados.
O nosso arquivo teste.txt conterá um cabeçalho (uma linha com um texto qualquer), e uma sequência de 10000 números aleatórios (obtidos com rand).
Repare que a forma de gravar é idêntica à maneira como escrevemos informações na tela via cout.
cout << "Gerando dados..." << endl;
// Grava o cabeçalho
arqsaida << "Cabecalho do arquivo" << endl;
// Agora grava os 10000 registros numéricos
for (int i = 0; i < 10000; i++)
{
int num = rand() % 10000;
arqsaida << i << setw(10) << num << endl;
if(arqsaida.fail()) {
cout << "Erro fatal!" << endl;
exit(1); // Aborta programa
}
}
Veja também neste ponto que empregamos o que se denomina modificador de formato: setw(10), que tem a função de ajustar a largura do próximo dado a ser enviado.
Nesse caso, ele serve para escrever cada valor com exatamente 10 caracteres, alinhados à direita (padrão para números inteiros).
A cada dado gravado, também verificamos se não houve nenhum erro, através da chamada a fail(), que retorna true se ocorreu algum erro de entrada ou saída.
Finalmente, o último (e importante) passo é fechar o arquivo, de forma que os dados que ainda estiverem na memória sejam enviados para o meio físico.
Se essa operação não for realizada, os últimos dados gravados no arquivo podem ser perdidos!
cout << "Fechando o arquivo..." << endl; arqsaida.close(); return 0; }
Lendo de um Stream
Como utilizaremos os mesmos headers do programa anterior, estes não serão novamente descritos aqui.
O primeiro passo então é criar um objeto ifstream, que representa o arquivo sendo lido, e associá-lo ao arquivo físico com a chamada open.
Repare que o processo é similar à gravação, porém emprega-se o parâmetro ios::in para indicar leitura e não gravação.
...
int main() {
// Cria input file stream (ifstream)
ifstream arq;
cout << "Abrindo arquivo texto..." << endl;
// Abre arquivo
arq.open( "teste.txt" , ios::in );
// Se houver erro, sai do programa
if (!arq.is_open())
return (0);
A seguir, é preciso ler primeiramente o cabeçalho.
Note que este deve ser lido com getline(), uma vez que é uma string com espaços em branco:
// Lê cabeçalho string cabecalho; getline(arq,cabecalho); // Exibe cabeçalho na tela cout << cabecalho << endl;
Uma vez lido o cabeçalho, sabe-se que os próximos registros são compostos de dois números: um contador e o valor armazenado.
Basta então fazer uma repetição, que terminará quando não houver mais dados no arquivo.
O método good retorna false quando algo diferente acontecer, por exemplo, um erro ou o próprio final do arquivo.
Repare que também só exibimos o dado lido na tela, se fail retornar false.
// Agora, lê n registros numéricos
do
{
int num, valor;
arq >> num >> valor;
if(!arq.fail())
{
cout << num << "\t" << valor << endl;
}
} while(arq.good());
Neste ponto, o laço pode ter terminado por dois motivos: ou houve um erro, ou o arquivo terminou. No primeiro caso, o método bad retorna true, e no segundo caso, o método eof retorna true. Então, uma situação de erro é quando bad retornou true, ou eof retornou false:
if(arq.bad() || !arq.eof())
{ cout << "Erro fatal!" << endl;
exit(1); // Aborta programa
}
Por fim, fechamos novamente o arquivo e encerramos o programa.
cout << "Fechando o arquivo..." << endl; arq.close(); return 0; }
Há muito mais detalhes do que foi apresentado nesta visão simplificada.
Explore outras subclasses de ios aqui.
Verifique os demais modificadores de formato (veja o item flags, depois clique em io stream format flags para ver a descrição de todos).
Verifique quais modificadores afetam quais tipos de dados e experimente gravar ints, floats, etc. Por que usamos getline ao invés de >> para ler a string ? E o que aconteceria nesse caso ? Troque no código e veja o resultado.
Exercícios
Implemente um sistema para contabilização de votos:
Ler os dados dos candidatos do arquivo candidatos.txt, armazenando as informações na memória; Ler os dados da votação de cada urna (arquivos urna[1-4].txt), acumulando os votos de cada candidato; Exibir na tela o relatório da votação: Candidato mais votado; Candidato menos votado; Percentual de votos do candidato mais votado em relação ao total; Zip com arquivos de dados para o exercício
O formato dos arquivos (por linha) é o seguinte:
Arquivo candidatos.txt: número_do_candidato nome_do_candidato nome_partido. Ex: 1 Roubalino PDR 2 Estelionatino PDR 3 Robaldo PD Arquivo urna[1-4].txt: número do candidato para este voto. Ex: 5 10 7 3 6 ...
Controle de estado de um fluxo
A classe ios descreve os aspectos comuns aos fluxos de entrada e saída.
É uma classe base virtual para todos os fluxos e, portanto, não podemos jamais criar um objeto dessa classe. Porém, podemos utilizar seus métodos para testar o estado de um fluxo ou para controlar o formato das informações.
Além disso, essa classe oferece uma série de constantes muito úteis na manipulação de fluxos.
A classe ios oferece os seguintes métodos:
| int good() | retorna um valor diferente de zero se a última operação de E/S teve sucesso |
|---|---|
| int fail() | faz o inverso do método anterior |
| int eof() | retorna um valor diferente de zero se o final do arquivo foi atingido |
| int bad() | retorne um valor diferente de zero se uma operação foi malsucedida |
| int rdstate() | retorna o valor da variável de estado do fluxo ou zero se tudo estiver bem |
| void clear() | zera o indicador de erros do fluxo |
Associando um fluxo a um arquivo
É possível criar um fluxo associado a um arquivo em disco. As três classes que permitem acessar arquivos em disco são definidas em fstream.h:
- ifstream: permite acessar arquivos para leitura.
- ofstream: permite acessar arquivos para gravação.
- fstream: permite acessar arquivos para leitura e gravação.
Cada uma dessas classes usa um buffer da classe filebuf para sincronizar as operações entre o fluxo e o disco.
A classe fstreambase oferece uma coleção de métodos comuns a essas três classes; em particular, o método open(), que abre um arquivo em disco e o associa a um fluxo:
void open(const char *nome, int modo, int prot=filebuf::openprot);
Nessa sintaxe, nome refere-se ao arquivo que será associado ao fluxo, modo indica o modo de seu abertura e prot define os direitos de acesso ao arquivo (por default, os direitos de acesso são estabelecidos pelo sistema operacional).
O modo de abertura deve ser um dos elementos da enumeração a seguir:
enum open_mode {
app, // anexa os dados ao final do arquivo
ate, // posiciona-se no final do arquivo
in, // abre para leitura (é o default para ifstream)
out, // abre para gravação (é o default para ofstream)
binary, // abre em modo binário (o default é o modo texto)
trunc, // destrói o arquivo se ele existe e o recria
nocreate, // se o arquivo não existe, a abertura falha
noreplace // se o arquivo existe, a abertura falha
};
Para a classe ifstream o modo de abertura default é ios::in e para a classe ofstream, é ios::out.
Exemplo:
#include <fstream.h>
ifstream in;
in.open("teste.tmp"); // abre para leitura, por default
ofstream out;
out.open("teste.tmp"); // abre para gravação, por default
fstream io;
io.open("teste.tmp", ios::in | ios::out); // abre para leitura e gravação
Podemos também chamar os construtores dessas três classes e combinar as operações de criação e abertura de fluxo numa única instrução:
Exemplo:
#include <fstream.h>
ifstream in("teste.tmp"); // abre para leitura, por default
ofstream out("teste.tmp"); // abre para gravação, por default
fstream io("teste.tmp", ios::in | ios::out); // abre para leitura e gravação
Formatação de dados
Cada fluxo conserva permanentemente um conjunto de indicadores especificando a formatação corrente. Isso permite dar um comportamento default ao fluxo, ao contrário do que ocorre com as funções printf() e scanf() da linguagem C, que exigem a indicação de um formato para cada operação de E/S realizada.
O indicador de formato do fluxo é um inteiro longo (protegido) definido na classe ios:
class ios {
public:
// ...
enum {
skipws, // ignora os espaços na entrada
left, // justifica as saídas à esquerda
right, // justifica as saídas à direita
internal, // preenche entre o sinal e o valor
dec, // conversão em decimal
oct, // conversão em octal
hex, // conversão em hexadecimal
showbase, // exibe o indicador de base
showpoint, // exibe ponto decimal nos números reais
uppercase, // exibe hexadecimais em maiúsculas
showpos, // exibe sinal em números positivos
scientific, // notação científica (1.234000E2) para reais
fixed, // notação fixa (123.4) para reais
unitbuf, // esvazia o fluxo após uma inserção
stdio // permite utilizar stdout et cout
};
//...
protected:
long x_flags; // indicador de formato
//...
};
A classe ios também define constantes através das quais acessamos os indicadores:
- static const long basefield : permite definir a base (dec, oct ou hex)
- static const long adjustfield :permite definir o alinhamento (left, right ou internal)
- static const long floatfield :permite definir a notação para reais (scientific ou fixed)
Os métodos seguintes (também definidos em ios) permitem ler ou modificar os valores dos indicadores de formato:
| long flags() | retorna o valor do indicador de formato |
|---|---|
| long flags(long f) | modifica os indicadores com o valor de f |
| long setf(long setbits, long field) | altera somente os bits do campo indicado por field |
| long setf(long f) | modifica o valor do indicador de formato |
| long unsetf(long) | zera o valor do indicador de formato |
Exemplo:
#include <iostream.h>
#include <iomanip.h>
void main(void)
{
cout.setf(ios::dec,ios::basefield);
cout << 255 << endl;
cout.setf(ios::oct,ios::basefield);
cout << 255 << endl;
cout.setf(ios::hex,ios::basefield);
cout << 255 << endl;
}
A execução desse programa produzirá a seguinte saída:
255 377 ff
Os métodos a seguir permite definir tamanho de campo e caracter de preenchimento:
| Int width(int) | define a largura do campo para a próxima saída |
|---|---|
| Int width() | devolve a largura do campo de saída |
| char fill(char) | define o caracter de preenchimento de campo |
| char fill() | devolve o caracter de preenchimento de campo |
| Int precision(int) | define o número de caracteres (menos o ponto) que um real ocupa |
| Int precision() | devolve o número de caracteres (menos o ponto) que um real ocupa |
Exemplo:
#include <iostream.h>
#include <iomanip.h>
void main(void)
{
cout << "(";
cout.width(4);
cout.setf(ios::right,ios::adjustfield);
cout << -45 << ")" << endl;
cout << "(";
cout.width(4);
cout.setf(ios::left,ios::adjustfield);
cout << -45 << ")" << endl;
cout << "(";
cout.width(4);
cout.setf(ios::internal,ios::adjustfield);
cout << -45 << ")" << endl;
}
Esse outro exibirá o seguinte:
( -45) (-45 ) (- 45)
Exemplo:
#include <iostream.h>
#include <iomanip.h>
void main(void)
{
cout.setf(ios::scientific,ios::floatfield);
cout << 1234.56789 << endl;
cout.precision(2);
cout.setf(ios::fixed,ios::floatfield);
cout << 1234.56789 << endl;
}
A saída exibe o valor em notação científica e em notação fixa:
1.2345678e+03 1234.57
Os manipuladores
O uso dos métodos de formatação leva a instruções um tanto longas.
Por outro lado, usando manipuladores podemos escrever um código mais compacto e mais legível.
Em vez de escrever:
cout.width(10);
cout.fill('*');
cout.setf(ios::hex,ios::basefield);
cout << 123 ;
cout.flush();
vamos preferir a instrução equivalente:
cout << setw(10) << setfill('*') << hex << 123 << flush;
As classes ios, istream e ostream implementam os manipuladores predefinidos.
O arquivo de inclusão iomanip.h define um certo número desses manipuladores:
| dec | a próxima operação de E/S usa base decimal |
|---|---|
| oct | a próxima operação de E/S usa base octal |
| hex | a próxima operação de E/S usa base hexadecimal |
| endl | escreve ‘\n’ e depois descarrega o buffer |
| ends | escreve ‘\0’ e depois descarrega o buffer |
| flush | descarrega o buffer |
| ws | descarta os espaços num fluxo de entrada |
| setbase(int b) | define a base para a próxima saída |
| setfill(int c) | define o caracter de preenchimento de campo |
| setprecision(int p) | define o número de dígitos na próxima saída de número real |
| setw(int l) | define a largura do campo para a próxima saída |
| setiosflags(long n) | ativa os bits do indicador de formato especificado por n |
| resetiosflags(long b) | desativa os bits do indicador de formato indicado por b |
Podemos também escrever nossos próprios manipuladores.
Por exemplo, um dos manipuladores mais utilizados, endl, é definido da seguinte maneira:
Exemplo:
ostream &flush(ostream &os) { return os.flush(); }
ostream &endl(ostream &os) { return os << '\n' << flush; }
Para utilizá-lo numa instrução do tipo:
cout << endl;
a função operator<< é sobrecarregada na classe ostream como:
Exemplo:
ostream &ostream::operator<<(ostream& (*f)(ostream &))
{
(*f)(*this);
return *this;
}
A função operator<< acima recebe como parâmetro um ponteiro para a função que implementa o manipulador a ser executado. Através desse ponteiro a função que implementa o manipulador é chamada e, depois, o objeto ostream é devolvido para que o operador possa ser usado de forma encadeada.
Vamos implementar, como exemplo, um manipulador para tabulação:
Exemplo:
ostream &tab(ostream &os)
{
return os << '\t';
}
Esse manipulador poderá ser usado assim:
cout << 12 << tab << 34;
Criando manipuladores
A implementação de um novo manipulador consiste em duas partes:
- manipulador: sua forma geral para ostream é:
ostream &<manipulador>(ostream &,<tipo>);
sendo <tipo> o tipo do parâmetro do manipulador.
Essa função não pode ser chamada diretamente por uma instrução de E/S, ela será chamada somente pelo aplicador.
- aplicador: ele chama o manipulador. É uma função global cuja forma geral é:
<x>MANIP( <tipo> )<manipulador>(<tipo> <arg>)
{
return <x>MANIP(<tipo>) (<manipulador>,<arg>);
}
com <x> valendo O para manipuladores de ostream (e seus derivados), I, S e IO para, respectivamente, para manipuladores de istream, ios e iostream.
Como exemplo, vamos criar um manipulador para exibir um número em binário:
Exemplo:
#include <iomanip.h>
#include <limits.h> // ULONG_MAX
ostream &bin(ostream &os, long val)
{
unsigned long mascara = ~(ULONG_MAX >> 1);
while ( mascara ) {
os << ((val & mascara) ? '1' : '0');
mascara >>= 1;
}
return os;
}
OMANIP(long) bin(long val)
{
return OMANIP(long) (bin, val);
}
void main()
{
cout << "2000 em binário é " << bin(2000) << endl;
}
| Funções Virtuais e Polimorfismo << | AULA 15 - Streams |
|---|


