7. Lendo e escrevendo dados¶
Sobre os exercícios desta lista
Nem todo compilador online tem um sistema de arquivos de verdade por trás. Por isso, a Lista 07 testa a lógica de leitura/escrita usando std::istringstream/std::ostringstream (que simulam um arquivo em memória) em vez de abrir arquivos de disco. O código com std::ifstream/std::ofstream deste capítulo funciona igual no seu computador — é só uma troca da "fonte" dos dados.
Escrevendo em arquivo: std::ofstream¶
Em C, você usava FILE*, fopen, fprintf, fclose:
Em C++, std::ofstream ("output file stream") funciona com os mesmos operadores << que você já usa com std::cout — porque std::cout também é um "stream", só que a saída dele é o terminal em vez de um arquivo:
#include <fstream>
std::ofstream arquivo("dados.txt");
if (arquivo) { // true se abriu com sucesso
arquivo << "Ana" << "," << 25 << "\n";
arquivo << "Bruno" << "," << 30 << "\n";
}
// o arquivo fecha sozinho quando "arquivo" sai de escopo (mais sobre isso na Unidade 5 -- RAII)
Repare: não existe fclose explícito. O destrutor de std::ofstream fecha o arquivo automaticamente quando a variável deixa de existir — você vai entender exatamente por que isso funciona no capítulo 10 (RAII), mas já pode confiar nesse comportamento agora.
Lendo de arquivo: std::ifstream¶
#include <fstream>
#include <string>
std::ifstream arquivo("dados.txt");
if (arquivo) {
std::string linha;
while (std::getline(arquivo, linha)) {
std::cout << linha << "\n";
}
}
std::getline(stream, variavel) lê uma linha inteira (até o \n) e a coloca em variavel. O laço while (std::getline(...)) é o padrão idiomático para "leia linha por linha até acabar o arquivo" — a condição do while é verdadeira enquanto a leitura tiver sucesso, e falsa quando chega ao fim do arquivo.
Lendo dados formatados (CSV simples)¶
Um arquivo .csv é texto com valores separados por vírgula. A ferramenta certa para separar uma linha em campos é std::istringstream — um "stream" que lê de uma std::string em vez de um arquivo:
#include <sstream>
#include <string>
std::string linha = "Ana,25";
std::istringstream stream(linha);
std::string nome;
std::getline(stream, nome, ','); // le ate a virgula (terceiro argumento = delimitador)
std::string idade_texto;
std::getline(stream, idade_texto); // le o resto da linha
int idade = std::stoi(idade_texto); // converte texto para int
std::stoi ("string to int") converte uma std::string numérica para int; existem equivalentes std::stod (para double), std::stol (para long), etc.
Juntando leitura de arquivo com parsing de CSV:
#include <fstream>
#include <sstream>
#include <vector>
#include <string>
struct Pessoa {
std::string nome;
int idade;
};
std::vector<Pessoa> le_pessoas(const std::string& caminho) {
std::vector<Pessoa> pessoas;
std::ifstream arquivo(caminho);
std::string linha;
while (std::getline(arquivo, linha)) {
std::istringstream stream(linha);
std::string nome, idade_texto;
std::getline(stream, nome, ',');
std::getline(stream, idade_texto);
pessoas.push_back({nome, std::stoi(idade_texto)});
}
return pessoas;
}
Por que isso importa: separar a lógica do "de onde vêm os dados"¶
Repare que le_pessoas recebe um caminho de arquivo — isso significa que ela só pode ser testada abrindo um arquivo de verdade no disco, o que nem sempre é possível (compilador online, ambiente de teste automatizado, etc.).
A solução idiomática é separar em duas partes: uma função que faz o parsing de um texto já em memória (fácil de testar, porque não depende de disco), e outra, bem mais fina, que só abre o arquivo e repassa o conteúdo:
// Parte testavel: recebe o CONTEUDO como string, nao o caminho do arquivo.
std::vector<Pessoa> parseia_pessoas(const std::string& conteudo) {
std::vector<Pessoa> pessoas;
std::istringstream stream(conteudo); // simula um arquivo em memoria
std::string linha;
while (std::getline(stream, linha)) {
std::istringstream linha_stream(linha);
std::string nome, idade_texto;
std::getline(linha_stream, nome, ',');
std::getline(linha_stream, idade_texto);
pessoas.push_back({nome, std::stoi(idade_texto)});
}
return pessoas;
}
// Parte fina, nao testada automaticamente: so conecta o arquivo real a logica acima.
std::vector<Pessoa> le_pessoas_de_arquivo(const std::string& caminho) {
std::ifstream arquivo(caminho);
std::string conteudo((std::istreambuf_iterator<char>(arquivo)),
std::istreambuf_iterator<char>());
return parseia_pessoas(conteudo);
}
Essa separação — lógica pura de um lado, "cano" de entrada/saída do outro — é exatamente o mesmo princípio que vai reaparecer com força total na Unidade 7, quando o "cano" for um sensor de hardware em vez de um arquivo. Vale a pena internalizar aqui.
Erros comuns¶
Esquecer de checar se o arquivo abriu — std::ifstream arquivo("nao_existe.txt") não lança erro nem trava o programa; arquivo simplesmente fica num estado de falha. Sempre confira com if (arquivo) antes de usar.
"terminate called after throwing an instance of ‘std::invalid_argument’" — std::stoi("abc") (texto que não é número) lança exceção. Se o dado pode vir corrompido, seria necessário tratar isso com try/catch (Unidade 5).
Misturar std::cin >> x com std::getline e ler uma linha vazia — o mesmo problema do capítulo 1: >> deixa o \n no buffer. Não é exclusivo de arquivo, mas aparece com frequência ao processar entrada linha por linha.
Linha com \r no final (arquivo salvo no Windows) atrapalhando o parsing — se o último campo de cada linha vier com um caractere estranho no fim, pode ser um \r sobrando (fim de linha do Windows). Fora do escopo deste capítulo, mas é um problema real que você pode encontrar ao processar arquivos de origens diferentes.
Resumo¶
std::ofstream/std::ifstreamfuncionam com<</>>estd::getline, igualstd::cout/std::cin— porque todos são "streams".- O arquivo fecha sozinho quando a variável stream sai de escopo (RAII — capítulo 10).
std::istringstreamlê de umastd::stringcomo se fosse um arquivo — é a ferramenta para fazer parsing de uma linha (ex.: CSV) e também para testar lógica de parsing sem precisar de arquivo real.std::stoi/std::stodconvertem texto para número.- Separe a lógica de parsing (recebe conteúdo como string, testável) da leitura de arquivo em si (fina, não testada automaticamente) — o mesmo padrão vai reaparecer com hardware na Unidade 7.