AULA 15 - Programação II - Graduação
Fluxos
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.
Fluxos e classes
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 et cerr são instâncias 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
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.
O exemplo a seguir ilustra a sobrecarga do operador de inserção em fluxo (>>) para a classe de números complexos, vista anteriormente:
Exemplo:
#include <iostream.h>
#include <math.h>
class Complexo {
public:
Complexo(double a, double b) { r=a; i=b; }
Complexo operator+(Complexo c) { return Complexo(r+c.r,i+c.i); }
friend ostream & operator<<(ostream &out,const Complexo &x) {
return out << x.r << (x.i<0 ? "-" : "+") << fabs(x.i) << "i";
}
private:
double r; // parte real
double i; // parte imaginária
};
void main(void)
{
Complexo a(1,2), b(3,-4);
cout << a+b << endl;
}
A função operator<< retorna uma referência ao objeto ostream para o qual foi chamada.
Isso permite chamá-la, de forma encadeada, várias vezes numa mesma instrução.
Exemplo:
void main(void)
{
Complexo a(1,2), b(3,-4);
cout << a << “ + ” << b << “ = “ << a+b << endl;
}
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
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 |
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:
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.
void open(const char *nome, int modo, int prot=filebuf::openprot);
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;
}


