Programar em C++/Entrada e saída de dados

Entrada e saída

editar

Aqui vamos dar início ao estudo de recursos que possibilitarão inserir dados e fazer reporte da falta deles.

No C++ a entrada e saída podem ser feitas através da biblioteca iostream. Para podermos usá-la deveremos colocar a linha de código: #include <iostream>

A estrutura de comunicação com o meio externo em modo texto é composta por um conjunto de objetos. Estas, em conjunto com operadores e funções de formatação possibilitam uma forma de comunicação mais intuitiva. Devido à abstração de elementos do mundo real por recursos da orientação a objetos, a forma de entender o código torna-se mais natural.

Na biblioteca iostream, temos os seguintes objetos:

  • cin - Este objeto fornece entrada de dados "bufferizada" através do "standard input device", o dispositivo de entrada padrão;
  • cout - Este objeto fornece saída de dados "bufferizada" através do "standard output device", o dispositivo de saída padrão;
  • cerr - Este objeto fornece saída de dados não "bufferizada" para o standard error device, o dispositivo de erro padrão, que é inicialmente definido para a tela.
  • clog - Este objeto fornece saída "bufferizada" através do "standard error device", o dispositivo de erro padrão que é inicialmente definido para a tela.

O foco de orientação a objetos que a biblioteca iostream confere aos dispositivos de entrada e saída é uma das características da linguagem C++. Ele está presente na maneira na qual o código foi idealizado e está formatado, modificando a maneira como as partes do sistema de entrada/saída interagem. Desta forma, as operações de interação entre o usuário e o software tornam-se mais intuitivas para o programador.

O sistema de entrada e saída é um exemplo deste modelo de programação, onde cada entidade física ou lógica de entrada e saída é representada por objetos cujas operações podem ser acessadas diretamente nos programas.

Buffer

editar

Para entendermos um pouco mais sobre Buffer, se faz necessário recordar um pouco sobre o funcionamento da memória e suas operações relacionadas a Buffer.

Bufferização é um meio de sincronização entre dispositivos de velocidades diferentes, tais quais memória e dispositivos de armazenamento mecânicos, como discos magnéticos. Para evitar que as operações do dispositivo mais lento interfiram no desempenho do programa pode-se fazer com que os dados sejam colocados em uma memória mais rápida e depois sejam enviadas ao dispositivo mais lento a medida que ele tenha disponibilidade para recebê-los, desta forma temos os seguintes modos de escrita em dispositivos de saída:

  • unbuffered – significa que qualquer mensagem ou dados serão escritos imediatamente. É o caso da escrita no dispositivo cerr;
  • buffered - significa que os dados serão mantidos num buffer de memória até que o dispositivo de destino solicite, ou que um comando de descarregamento seja executado, ou quando o buffer estiver cheio. O problema é que se o programa é interrompido antes do buffer ser escrito esses dados são perdidos.
cout << "hello";	// mostra a palavra hello no ecrã(monitor)
cout << 120; 		// mostra o número 120 no ecrã(monitor)
cout << hello;	// mostra o conteúdo do pedaço de memoria a que chamamos de "hello" no ecrã(monitor)
cout << "hello, tenho " << age<< " anos de idade"; 	/* mostra a primeira string depois 
                                                           vai buscar o conteúdo da variável 
                                                           age de depois a string “anos de idade”
                                                        */
cout << "Primeira frase. ";
cout << "Segunda frase.\n" << "Terceira frase."; /* imprime:
                                                     Primeira frase. Segunda frase.
                                                     Terceira frase.
                                                  */


O cout (c+out) usado em conjugação com o operador de inserção “<<” permite enviar dados para o "stream out" que por definição é o ecrã (monitor).

Então podemos enviar as constantes, as variáveis, a conjugação das duas se nos apetecer, separadas pelo operador de inserção.

Temos ainda diversos recursos de formatação através de "escapes sequences" que detalharemos no tópico logo a seguir, o recurso usado aqui concatena as várias frases na mesma linha. Temos de dizer explicitamente "quebra de linha", através do "\n", que faz com que a sequência logo após, seja escrita na próxima linha.

Uma característica muito importante do C++, presente nas instruções logo acima, é o polimorfismo notável na operação de apresentação dos dados na saída; Note que os tipos de dados que são passados para o cout são diversos, ou seja, não importa qual o tipo de dado que será entregue ao cout, de alguma maneira ele sempre formatará de uma maneira legível no monitor. Nos capítulos mais adiante veremos como fazer com que tipos de dados diferentes sejam tratados pelo mesmo objeto.

Escape Sequences

editar

Há um conjunto de caracteres, nós chamamos de string. Mas no exemplo anterior quando usamos o "\n", nós antes dissemos que o cout com o operador << iria colocar no monitor todos os caracteres que estivessem entre aspas. Acontece que existem estas strings especiais – chamadas de "escape sequences" - que de alguma forma alteram o sentido das strings. Existem muitas destas sequências. As mais conhecidas são estas:


Escape Sequences (as mais comuns)

  • \n nova linha muda o cursor para uma linha abaixo
  • \r retorno
  • \t tabulador muda o cursor para o próximo ponto de tabulação
  • \v tabulador vertical
  • \b deleção reversa
  • \f alimentador de página
  • \a alerta (bipe) faz o computador emitir um sinal sonoro
  • \' aspas simples (') imprime aspas simples
  • \" aspas duplas (") imprime aspas duplas
  • \? sinal de interrogação (?)
  • \\ barra oposta (contrabarra) (\)

O objeto cin obtém informação do "standard input" (que usualmente é o teclado). Este objeto está tal como o cout declarado no cabeçalho da biblioteca <iostream>

A sintaxe mais comum da instrução para obter dados do cin é:

  • cin >> [variable name];

Aqui temos o operador de extração ">>" que diz que tudo o que o teclado escrever, coloque esses dados na variável que me segue. Este operador consegue até traduzir o conceito de dados de fora para dentro.

#include <iostream>

using namespace std;

int main(void)
 {
   int testScore;
   cin >> testScore;
   cout << "Your test score is " << testScore << "\n";

#ifdef WIN32
   system ("pause"); /* Necessário apenas para sistemas Microsoft®, em modo gráfico.
                        Em UNIX®, variantes e similares use um terminal de texto e 
                        esta função não será necessária. */
#endif

   return 0;
 }

Há mais um pormenor. O computador está á espera de um "Return" ("ENTER", ou "New Line", ou Espaço em Branco ) para finalizar a entrada de dados na variável, até lá o cursor aparece a piscar.

Bem, na verdade, este ponto é muito importante, por que… Vejamos mais a baixo a questão de termos 2 entradas.


Pergunta: declaramos uma variável int testScore e se colocarmos um valor que não seja um int? Isto não é a mesma situação do capítulo anterior porque antes o programa ainda não tinha compilado, e agora temos entrada de dados quando o programa já está compilado e a correr/rodar.

Assim se no exemplo anterior colocarmos o nome “Jeff”, que é uma string, e o programa está a espera de um int, o que acontece é que o cin não vai colocar "jeff" na variável (ele ignora a entrada). E quando o cout é chamado ele vai colocar o valor que está na variável.

Então porque é que me apareceu o número –858993460 quando corri/rodei o programa? É que na memória física do computador existem dados da área onde a variável está alocada fisicamente, e quando declarei o testScore o compilador apenas reserva aquela memória mas não apaga o que lá está.

Pergunta: O que é que acontece quando inserimos um número maior do que o limite do tipo quando o programa executar? É o caso de "overflow" - estouro de memória, mas quando o programa corre/roda.

Aqui já não temos a questão de dar a volta ao intervalo permitido, aqui temos o caso em que vai ser colocado um número estranho. Isto não está perfeitamente explicado.

Chama-se prompt quando é dito ao utilizador o que deve fazer, o que não deixar como no exemplo anterior o cursor a piscar sem o utilizador saber o que fazer. Além de que temos o problema de overflow em execução, portanto é bom que o utilizador cumpra os requerimentos.

De uma maneira muito conveniente, os dados recebidos pelo cin são tratados de forma a tornar o processo polimórfico, da mesma forma que no caso de cout, assim temos como receber os dados da maneira que precisamos, ou seja, quando declaramos uma variável int e a usamos para receber um dado do cin, o mesmo é convertido na entrada para inteiro, quando usamos uma variável de outro tipo, a entrada é convertida para o tipo da variável.

Lendo um caractere

editar

Ler um caractere até é simples, basta utilizar o objeto cin e será guardado o valor digitado na variável.

   char nivel;
   cout << "Entre um nível: ";
   cin >> nivel;

Porém teremos de pressionar a tecla ENTER depois de digitar o caractere. Isto leva a várias questões.


O problema “pressione uma tecla para continuar...”

 #include <iostream>

 using namespace std;

 int main(void)
 {
   char ch;
   do {
      cout << "Pressione S ou s para sair, qualquer outra tecla para continuar: "; 
      cin >> ch; 
      if (ch != 'S' && ch != 's')
         cout << "Deseja continuar?"<<endl;
      else
         cout << "Saindo..."<<endl;
   } while (ch != 'S' && ch != 's');

#ifdef WIN32
  system ("pause");
#endif

  return 0;
 }
  • O programa funciona bem se pressionarmos S ou s para sairmos;
  • O programa funciona bem se pressionarmos qualquer outra tecla com caractere imprimível;
  • Mas se pressionarmos a tecla ENTER, nada acontece, o cin continua à espera de entrada. A razão é o operador de extração ">>" ignora os espaços em branco e os caracteres "nova linha" resultantes do pressionamento da tecla enter.

A função cin.get()

editar

Já tivemos oportunidade para discutir a função getline (função membro) do objeto cin.

cin.getline(name,80);

Aqui vamos utilizar uma outra função, a cin.get().

Esta função pode ser chamada, tal como a getline(), através de 3 argumentos, onde o primeiro é o array de caracteres, mas também o pode ser sem argumentos ou ainda apenas um argumento.

No caso de não conter argumentos apenas irá ler um caractere, em vez de uma cadeia de caracteres.

No caso de ter um argumento, ela aceita qualquer tecla incluindo o enter. (o que não se passa com o cin e o operador de extração). Aqui um exemplo

 #include <iostream>

 using namespace std;
 
 int main(void)
 {
   char ch;
   do {
      cout << "Pressione S ou s para sair, \nqualquer outra tecla para continuar: "; 
      cin.get(ch);
      if (ch != 'S' && ch != 's')
         cout << "Deseja continuar?"<<endl;
      else
         cout << "Saindo..."<<endl;
   } while (ch != 'S' && ch != 's');

#ifdef WIN32
  system ("pause");
#endif


   return 0;
 }

Porém se pressionarmos uma tecla de caractere imprimível, não conseguiremos inserir o próximo prompt, parece que houve um salto. Estranho!

Para explicar a razão deste novo problema necessitamos de explicar o conceito de buffer.

O "input buffer" é uma área de memória que guarda os caracteres de entrada, por exemplo do telado, até que essa entrada seja atribuída pelo cin e o operador de extração >>, ou por funções como get() ou getline() do objeto cin.

Quando o loop começa, o "input buffer" está vazio.

  • Se digitarmos apenas o enter, sendo este o primeiro e único caractere no "imput buffer", ele é removido do input buffer e atribuído á variável ch, então o "input buffer" está vazio na próxima iteração do loop;
  • Se digitarmos x e enter. Temos 2 caracteres. A função get() retira o primeiro caractere do "input buffer" e atribui à variável ch, mas nisto o caractere nova linha permanece no "input buffer". Isto faz com que na próxima iteração do loop, não haja a oportunidade para entrar com dados.

Ou seja, na segunda iteração, é retirado o caractere nova linha – que ficou da 1ª iteração - e é colocado na variável ch. Agora o "input buffer" está vazio.

cin.ignore()

editar

Uma solução é limpar o caractere nova linha do "input buffer" antes da chamada da função getline(). E fazemos isso usando a função ignore() do objeto cin.

Esta função membro tal com a get() e a getline() são sobrecarregadas, podem ser chamadas sem argumentos, com um ou dois argumentos.

Utilizar a função ignore() sem argumentos, permite que o próximo caractere no "input buffer" seja lido e depois descartado,- e isto é exatamente aquilo que queríamos.

A função com 1 ou 2 argumentos é usada para cadeias de caracteres.

  • Com um argumento, o argumento é o número máximo de caracteres a ser removido do "input buffer". Exemplo:
 cin.ignore(80); // Remove até 80caracteres do input buffer
  • Com dois argumentos, o segundo argumento é o delimitador, um caractere que se encontrado, antes do número de caracteres especificado no primeiro paramento, faz com que a remoção pare. Exemplo:
cin.ignore (80, '\n'); // Remove 80 caracteres se até lá não encontrar o nova linha.

Reescrevendo o código anterior utilizando o cin.ignore()

 #include <iostream>

 using namespace std;

 int main(void)
 {
   char ch;
   do {
      cout << "Pressione S ou s para sair,\n qualquer outra tecla para continuar: "; 
      cin.get(ch);
      cin.ignore();
      if (ch != 'S' && ch != 's')
         cout << "Deseja continuar?"<<endl;
      else
         cout << "Saindo..."<<endl;
   } while (ch != 'S' && ch != 's');

#ifdef WIN32
  system ("pause");
#endif


   return 0;
 }

Ora este programa funciona muito bem, MAS…

Se pressionarmos a tecla Enter para continuar, teremos de fazer isso duas vezes, pois a primeira vez é ignorada. A razão: é que não existe nada no "input buffer" quando a função ignore é chamada, por isso é que a tecla enter necessita de ser pressionada 2 vezes, colocando um caractere nova linha a mais no "buffer" para a função ignore() remover.

Se tentarmos modificar isto através do if?

 #include <iostream>

 using namespace std;

 int main(void)
 {
   char ch;
   do {
      cout << "Pressionar S ou s para sair,\n qualquer outra tecla para continuar: "; 
      cin.get(ch);
      if (ch != '\n')
         cin.ignore();
      if (ch != 'S' && ch != 's')			
         cout << "Deseja continuar?"<<endl;
      else
         cout << "Saindo..."<<endl;
   } while (ch != 'S' && ch != 's');

#ifdef WIN32
  system ("pause");
#endif

   return 0;
 }

Agora sim temos todos os problemas resolvidos e isto agora funciona!!

cin, cin.get(), cin.getline()

editar

O problema anterior do caractere nova linha permanece quando usamos o cin, o get() e o getline() juntos num programa, uma vez que o enter é usado para terminar a entrada.

 #include <iostream>

 using namespace std;

 int main(void)
 { char name[80];
   int courseNum;
   cout << "Informe o número do curso: ";
   cin >> courseNum;
   cout << "Informe seu nome: ";
   cin.getline(name, 80);
   cout << "O número do curso é: " << courseNum << endl;
   cout << "Seu nome é: " << name << endl;

#ifdef WIN32
  system ("pause");
#endif

   return 0;
 }

Aqui, neste exemplo, nós não tivemos a oportunidade de colocar o nome. Quando digitamos o número e depois pressionamos a tecla enter, o cin coloca o número no courseNUm mas o caractere nova linha permanece no "input buffer", que fica para o enter name, pois o getline lê espaços em branco.

A solução pode ser:

 #include <iostream>

 using namespace std;

 int main(void)
 {
   char name[80];
   int courseNum;
   cout << "Informe o número do curso: ";
   cin >> courseNum;
   cin.ignore();
   cout << "Informe seu nome: ";
   cin.getline(name, 80);
   cout << "O número do curso é: " << courseNum << endl;
   cout << "Seu nome é: " << name << endl;

#ifdef WIN32
  system ("pause");
#endif

   return 0;
 }

A partir destes exemplos podemos criar umas regras:

  1. Colocar sempre a função ignore() depois do cin e do >>;
    • Razão: O "cin>>" deixa sempre o nova linha no "input buffer". Assim devemos eliminá-lo com a função ignore().
  2. Não colocar a função ignore(), no caso de ser sem parâmetros, depois do getline();
    • Razão:O getline() remove o caractere nova linha que termina a entrada do "input buffer", portanto não é necessário o ignore().
  3. Verificar se temos o caractere nova linha no "input buffer" depois de utilizar o get(), se tivermos deveremos utilizar o ignore().
    • Razão: A função get() com um argumento deixa o caractere nova linha no "input buffer" se pressionarmos um caractere e o enter. mas não deixará, se apenas pressionarmos o enter. portanto é necessário confirmar.

Entrada de valores para variáveis múltiplas

editar

Podemos fazer com que o programa receba vários valores ao mesmo tempo

cin >> [first variable] >> [second variable] >> [third variable];

Neste caso o utilizador separa os dados por espaços (o enter também dá) e como anteriormente o utilizador fecha utilizando o enter

 #include <iostream>
 #include <string> 

 using namespace std;

 int main(void)
 {
   int peso, altura;
   string nome;

   cout << "escreva o seu nome, peso e altura \n separe os valores por espaços\n";
   cin >> nome >> peso >> altura;
   cout << "o seu nome é:" << nome << "\n";
   cout << "o seu peso é:" << peso << "\n";
   cout << "a sua altura é: " << altura<< "\n";

#ifdef WIN32
   system ("pause"); /* Necessário apenas para sistemas Microsoft®, em modo gráfico.
                        Em UNIX®, variantes e similares use um terminal de texto e 
                        esta função não será necessária. */
#endif

   return 0;
 }

Note-se que no exemplo anterior poderíamos colocar as 3 variáveis, mesmo no caso de elas serem de tipos diferentes. Temos de ter atenção na ordem de entrada.

Pergunta: se escrevêssemos duas palavras para o nome, apenas a primeira é que apareceria a 2 palavra que estava separada da primeira para um espaço seria colocada na variável peso.