Input/Output (Entrada/Saída)
O termo I/O
se refere aos dispositivos de Entrada (Input) e Saída (Output). Essa seção traz
uma introdução para trabalhar com a leitura de dados do usuário pelo terminal (CMD) e a escrita de dados em terminal e
em arquivos de texto.
A forma mais comum de escrever dados no terminal, em C++, é a partir de std::cout
("Character Output"), e é a
forma como vêm sendo escritos os exemplos desse livro desde os primeiros capítulos.
Para utilizar o std::cout
, é necessário incluir o header <iostream>
, como mostra o exemplo abaixo.
#include <iostream>
int main()
{
std::cout << "Hello World!\n"; // Escreve "Hello World!" e pula uma linha (\n)
return 0;
}
A sintaxe é diferente em relação às chamadas de função normalmente utilizada. Note que ao invés de std::cout("texto")
,
escreve-se std::cout << "texto";
. Isso se dá pela forma como std::cout
foi implementado. Na verdade, ele é um
objeto que possui um operator<<
, e não uma função. Essa informação é apenas a título de curiosidade, e não afeta o uso
do std::cout
.
Não é necessário converter os dados para string para utilizar o std::cout
. Ele consegue lidar com dados de outros
tipos, como int
e float
. Basta escrever std::cout << my_int;
, sendo my_int
uma variável do tipo
int
, por exemplo.
É possível ler dados do usuário a partir de std::cin
("Character input"), como no exemplo abaixo.
Os dados lidos podem ser de qualquer tipo primitivo ou string. Os dados podem ser separados por quebra de linha ou por
espaço.
#include <iostream>
#include <string>
int main()
{
using namespace std::string_literals;
std::cout << "Digite os 3 dados:\n";
auto my_int = 0;
auto my_float = 0.;
auto my_string = ""s;
std::cin >> my_int >> my_float >> my_string;
std::cout << "Você digitou: " << my_int << ", " << my_float << ", " << my_string << "\n";
return 0;
}
Note que até agora todos os programas apresentados guardavam as suas variáveis em memória. Isso significa que, ao
reiniciar o programa, todos os cálculos com as variáveis são perdidos. Uma das formas de persistir (guardar) dados em
disco é utilizando o fstream
, como no exemplo abaixo. Claro que isso é uma forma bastante primitiva de persistir
dados. É importante lembrar que existem softwares de banco de dados e bibliotecas de gerenciamento de arquivos
mais robustas. Mesmo assim, é interessante conhecer a biblioteca padrão, por ser uma alternativa válida para a
persistência de dados em algumas situações. É importante apenas manter em mente que não é a única e nem necessariamente
a melhor ferramenta para resolver qualquer problema.
#include <iostream>
#include <fstream>
#include <string>
int main() {
using namespace std::string_literals;
auto filename = "arquivo.txt"s;
auto file_stream = std::fstream(filename, std::ios::out);
if (!file_stream.is_open()) {
std::cout << "Failed to open " << filename << '\n';
return -1;
}
auto variable = 1.234;
file_stream << "Conteúdo do arquivo!\n"s;
file_stream << variable;
return 0;
}
O exemplo acima abre um arquivo chamado arquivo.txt
em modo de saída (std::ios::out
). O programa verifica se foi
possível abrir o arquivo e, caso negativo, gera uma mensagem e sai do programa com um código de erro (return -1
).
Uma vez aberto, escreve-se uma string de exemplo,
e uma variável do tipo float
. Note que o arquivo não é explicitamente fechado (close
). Isso por que o
fstream
garante que o arquivo será fechado ao fim do escopo de vida do mesmo. Dessa forma, não é necessário fechar
o arquivo explicitamente.
O código capaz de gerar um programa que faz a leitura do mesmo arquivo é mostrado abaixo. Na verdade, essa é apenas uma
das formas de fazer a leitura dos dados de um arquivo. Também é possível ler o conteúdo de um arquivo caracter por
caracter. Para isso, poderia-se utilizar o método file_stream.get(l);
, com l
sendo do tipo char
. Cada forma de
leitura tem suas vantagens e desvantagens, e seu uso vai depender do problema que se está tentando resolver.
#include <iostream>
#include <fstream>
#include <string>
int main() {
using namespace std::string_literals;
auto filename = "arquivo.txt"s;
auto file_stream = std::fstream(filename, std::ios::in);
if (!file_stream.is_open()) {
std::cout << "Failed to open " << filename << '\n';
return -1;
}
auto line = ""s;
auto i = 1;
while(!file_stream.eof()) {
getline(file_stream, line);
std::cout << "Linha " << i << ": " << line << '\n';
i += 1;
}
return 0;
}
Estudo de Caso: Input de dados para juizes online
Existem vários sites na internet para exercitar a prática de programação e algoritmos. Esses sites são conhecidos como "juizes online" como, por exemplo, UVa e Spoj. Nesses sites, é comum encontrar problemas com um formato bastante específico para os inputs dos dados dos desafios. Essa subseção traz um breve tutorial para iniciantes em C++, em relação a como poderia-se fazer a leitura dos dados para esses tipos de problema.
Para o exercício, considere o problema PRIME1 do Spoj, copiado (e traduzido) abaixo, para referência:
Peter deseja gerar todos os números primos para seu sistema. Você deve ajuda-lo. Gere todos os números primos entre dois números dados.
Entrada
A entrada inicia com um número
t
, que representa o número de testes (t <= 10). Cada uma dast
próximas linhas contém dois números,m
en
, separados por espaços.Saída
Para cada caso de teste, escreva na tela todos os primos
p
ondem <= p <= n
. Cada número primo deve ser escrito em uma linha diferente. Casos de teste devem ser separados por uma linha em branco.Exemplo
Entrada:
2
1 10
3 5Saída:
2
3
5
73
5
O objetivo aqui não é resolver o problema completamente, apenas implementar a leitura dos dados. A entrada de
dados segue um padrão bem definido, conforme o exemplo acima. A primeira linha contém apenas um número, que deve ser lido na
variável t
. As t
linhas seguintes devem conter pares de números (m, n)
.
Conforme visto, é possível fazer a leitura dos dados a partir do std::cin
. Utiliza-se, então, essa forma para fazer
a leitura da variável t
:
#include <iostream>
int main() {
auto t = 0;
std::cin >> t;
std::cout << "CASOS DE TESTE: " << t << '\n';
return 0;
}
A seguir, para fazer a leitura dos pares m
e n
, é possível utilizar um for
com um contador de i = 0
até t
.
Enquanto esse contador não chegar no valor de t
, lê-se um novo par de valores. Assume-se que os valores serão dois
números válidos (Não é feita nenhuma verificação sobre os valores).
#include <iostream>
int main() {
auto t = 0;
std::cin >> t;
std::cout << "CASOS DE TESTE: " << t << '\n';
for (auto i = 0; i < t; ++i) {
auto n = 0;
auto m = 0;
std::cin >> n >> m;
std::cout << "Processando dados: (" << n << ", " << m << ")\n";
// ... resto do código ...
}
return 0;
}
Uma curiosidade interessante é que, no linux, é possível redirecionar o conteúdo de um arquivo texto, de forma que ele
sirva de entrada para um programa. Por exemplo, digamos que você compile o arquivo acima e gere um executável chamado
programa
. Ao invés de executar esse programa e digitar as entradas dos dados manualmente, é possível criar um arquivo
(por exemplo, input.txt
) cujo conteúdo é conforme dado abaixo, e redirecionar o conteúdo desse arquivo para a entrada
do programa programa
, da seguinte forma: ./programa < input.txt
.
2
1 10
3 5
O resultado da execução é dado abaixo. Essa é uma forma rápida de manter arquivos de teste para problemas de maratona de programação e juizes online.
$ ./programa < input.txt
CASOS DE TESTE: 2
Processando dados: (1, 10)
Processando dados: (3, 5)