Resumo
Tudo o que um programa calcula desaparece quando ele termina, a menos que o deixe escrito em algum lugar que lhe sobreviva. Esta aula apresenta esse lugar, o arquivo de texto, e a ideia que permite usá-lo sem aprender nada radicalmente novo: um arquivo é lido e escrito como um fluxo, com as mesmas operações que o teclado e a tela. Estudam-se std::ifstream e std::ofstream, a verificação de que um arquivo foi aberto antes de usá-lo e a pasta de trabalho, que decide onde ele é procurado; o laço de leitura correto, while (std::getline(in, line)), em contraste com o laço com eof(), que processa um dado a mais, e a razão exata desse dado; a escrita com std::println, o modo de acrescentar e o fechamento ao sair do bloco. Encerram a aula os formatos de linha: separar campos com vírgulas, validá-los com std::from_chars e decidir, na especificação, o que fazer com as linhas malformadas.
Objetivos de aprendizagem
- Abrir um arquivo para ler ou para escrever e verificar que foi aberto antes de usá-lo, distinguindo o arquivo que não existe daquele que é procurado em outra pasta.
- Ler um arquivo linha por linha com
while (std::getline(in, line))e explicar, acompanhando o estado do fluxo, por quewhile (!in.eof())processa um dado a mais. - Escrever resultados em um arquivo com
std::println(out, …), escolhendo entre substituir seu conteúdo e acrescentar ao final. - Processar um arquivo de valores separados por vírgulas em um vetor de registros, validando cada linha e informando as inválidas com seu número.
Um arquivo é um fluxo
A persistência
As variáveis de um programa vivem na memória, e a memória de um programa é liberada quando ele termina: a próxima execução começa sem se lembrar de nada. Para que um dado sobreviva ao programa que o produziu, ele deve ficar em um meio permanente, e o meio de uso geral é o arquivo: uma sequência de bytes com nome, guardada no disco, que o sistema operacional conserva entre execuções. Um arquivo de texto é aquele cujos bytes são caracteres, em UTF-8 como se viu na aula O texto não é ASCII, organizados em linhas separadas pelo caractere '\n'. É o formato mais modesto e o mais duradouro: qualquer editor o abre, qualquer linguagem o lê, e uma pessoa pode revisá-lo a olho nu.
Ora, C++ não trata o arquivo como uma coisa distinta do teclado. Desde a aula Entrada, saída e erros, std::cin é um fluxo de entrada: uma fonte de caracteres que são consumidos em ordem, com um estado que registra se a última operação falhou. Um arquivo aberto para leitura é outro fluxo de entrada, do tipo std::ifstream (de input file stream); um arquivo aberto para escrita, um fluxo de saída, do tipo std::ofstream. Ambos são declarados no cabeçalho <fstream>. Daí se segue que tudo o que se aprendeu sobre std::cin na aula Ler bem, o operador >>, std::getline e o estado de erro, vale sem mudanças para um arquivo.
Abrir e verificar
Um arquivo é aberto ao criar o fluxo, com o nome do arquivo entre chaves: std::ifstream in{"grades.txt"};. Abrir pode falhar (o arquivo não existe, não há permissão para lê-lo, o nome está mal escrito), e a falha não detém o programa nem produz mensagem alguma: deixa o fluxo em estado de erro. Um fluxo em erro, usado como condição, vale false; portanto, a verificação é if (!in), e deve ser feita antes da primeira leitura. O programa seguinte tenta abrir um arquivo que não existe.
#include <fstream>#include <iostream>#include <print>int main(){ std::ifstream in{"grades.txt"}; if (!in) { std::println(std::cerr, "Não foi possível abrir grades.txt"); return 1; } int first{}; in >> first; std::println("Primeira nota: {}", first);}Não foi possível abrir grades.txtO programa terminou com o código 1.
Três detalhes merecem atenção. O primeiro é std::cerr (de <iostream>), o fluxo de erros: escreve na tela como a saída padrão, mas é um canal distinto, de modo que as mensagens de erro não se misturam com os resultados quando a saída é redirecionada para um arquivo; std::println aceita como primeiro argumento o fluxo em que deve escrever. O segundo é o return 1;: um código de término diferente de zero, como se viu na aula Anatomia de um programa, avisa a quem executou o programa que algo deu errado. O terceiro é o que teria acontecido sem a verificação: in >> first sobre um fluxo em erro não faz nada, e o programa teria escrito “Primeira nota: 0” com toda a tranquilidade. Um resultado falso com aparência de verdadeiro é pior que uma mensagem de erro; donde a regra desta aula: todo arquivo é verificado ao ser aberto.
Caminhos relativos e a pasta de trabalho
O nome "grades.txt" não diz em que pasta está o arquivo. Um nome assim é um caminho relativo, e é resolvido a partir da pasta de trabalho: a pasta a partir da qual o programa é executado, que não é necessariamente a pasta do executável nem a do código-fonte. O programa seguinte, guardado na pasta project junto a um arquivo data.txt, abre esse arquivo e escreve sua primeira linha; foi executado duas vezes, primeiro a partir de project e depois a partir da pasta superior.
$ cd project$ g++ -std=c++23 -Wall -Wextra -Wpedantic -Wconversion -Wshadow -g -fsanitize=address,undefined -fno-sanitize-recover=undefined main.cpp -o main$ ./mainPrimeira linha: Olá do arquivo$ cd ..$ ./project/mainNão foi possível abrir data.txtO executável é o mesmo, e o arquivo continua em seu lugar; a única coisa que mudou é a pasta de trabalho, e com ela o lugar onde se procura data.txt. Esta é a causa mais frequente do “não encontra o arquivo”. Um editor que executa o programa com um botão escolhe por conta própria a pasta de trabalho (no Visual Studio Code costuma ser a pasta aberta no editor, embora dependa da configuração da tarefa), de modo que convém executar a partir do terminal, na pasta dos dados, até saber o que o editor faz. A tentação contrária, escrever no código um caminho absoluto como "/home/ana/curso/data.txt" ou "C:\\Users\\Ana\\data.txt", resolve o problema em uma única máquina e o cria em todas as outras: o programa deixa de funcionar assim que outra pessoa o executa ou a pasta é movida. O recomendável é o caminho relativo com uma pasta de trabalho conhecida ou, melhor, o nome do arquivo como dado fornecido pelo usuário.
Carregando o conteúdo…