AULA 15 - Programação II - Graduação

De IFSC
Revisão de 13h38min de 13 de junho de 2018 por imported>Fargoud (→‎>> Operador de Extração)
Ir para navegação Ir para pesquisar

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:

PRGfluxo1.png
  • 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:

PRGfluxo2.png
  • 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.

PRGfluxo3.png
  • 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

<<= Página do curso