1. Para que precisamos de arquivos, se existe std::cin?
Quando você escreve programas de estudo, parece que a entrada pelo teclado será suficiente para sempre. Mas imagine um usuário real: ele adicionou 20 tarefas à lista, fechou o programa, abriu novamente… e viu tudo vazio. Isso é frustrante, mesmo se o usuário for você. Arquivos permitem que a aplicação “lembre” do estado entre execuções: salvar configurações, dados, resultados de cálculo e outras coisas importantes.
Em C++ os arquivos são conectados ao programa através da interface de streams — muito parecida com std::cin e std::cout. Não vamos aqui aprofundar em formatos e tratamento de erros de leitura (isso é assunto à parte), mas vamos aprender o essencial: como abrir corretamente um arquivo, verificar que ele abriu, e garantir o fechamento correto via RAII.
Fluxos de arquivo de <fstream>
Se <iostream> nos dá streams para console, <fstream> fornece os streams para arquivos. Em termos de “quem faz o que” é bem simples: existe um stream para leitura, um para escrita e um para ambos. É importante não confundir suas funções — mais ou menos como não confundir chave de fenda e martelo. Às vezes você até consegue usar a chave de fenda para pregar um prego… mas é melhor não fazer isso.
| Classe do stream | Propósito | Anologia simples |
|---|---|---|
|
ler do arquivo | “como std::cin, só que de arquivo” |
|
escrever no arquivo | “como std::cout, só que para arquivo” |
|
ler e escrever | “um cabo universal, mas pense antes de usar” |
O “esqueleto” mínimo de um programa com arquivos é assim:
#include <fstream>
#include <iostream>
int main() {
std::ifstream in("input.txt");
std::ofstream out("output.txt");
std::cout << "Streams criados\n"; // Streams created
}
Por enquanto não verificamos nada — isso é intencional. Agora vamos adicionar segurança.
2. Abrindo arquivo e checando o resultado
Quando você encontra fluxos de arquivo pela primeira vez, surge a pergunta: “Qual a forma correta de abrir um arquivo?” Boa notícia: existem exatamente duas formas “normais”, e ambas são usadas em código real. A diferença não é mágica, é conveniência: às vezes o nome do arquivo é conhecido imediatamente, às vezes vem de uma variável (por exemplo, das configurações) e aí faz sentido abrir mais tarde.
Abrir no construtor
Esse é o modo mais comum: criamos o stream e já passamos o nome do arquivo.
#include <fstream>
#include <iostream>
int main() {
std::ifstream in("notes.txt");
if (!in) {
std::cerr << "Não é possível abrir notes.txt\n";
return 1;
}
std::cout << "Arquivo aberto\n"; // File opened
}
Aqui já fica clara uma coisa importante: o stream pode ser verificado como bool.
Abrir depois via open()
Essa opção é útil quando o nome do arquivo é calculado ou escolhido mais tarde.
#include <fstream>
#include <iostream>
#include <string>
int main() {
std::string filename = "notes.txt";
std::ifstream in;
in.open(filename);
if (!in) {
std::cerr << "Não é possível abrir " << filename << "\n";
return 1;
}
}
As duas formas são equivalentes. Para quem está começando, “no construtor” costuma ser mais legível.
Como checar a abertura: if(!stream) e is_open()
Há uma verdade dura sobre arquivos: o arquivo pode não abrir. As razões costumam ser banais: arquivo não existe, sem permissões, caminho incorreto, ou você esperava um diretório diferente ao executar o programa. Se você não checar a abertura, leitura/escrita simplesmente “não vão funcionar”, e você ficará olhando para o código pensando “mas eu escrevi certo”.
Checar com if(!stream) é a forma mais prática, porque verifica o “estado geral” do stream:
#include <fstream>
#include <iostream>
int main() {
std::ofstream out("log.txt");
if (!out) {
std::cerr << "Não é possível abrir log.txt para escrita\n";
return 1;
}
out << "Olá arquivo\n";
}
Checar com is_open() é um pouco mais específico: verifica exatamente “o arquivo está aberto?”.
#include <fstream>
#include <iostream>
int main() {
std::ifstream in("data.txt");
if (!in.is_open()) {
std::cerr << "data.txt não está aberto\n";
return 1;
}
}
Ambas as formas são aceitáveis. Em tarefas didáticas costuma-se usar if(!in), porque depois geralmente se verificam outros estados do stream também.
4. RAII e fechamento correto do arquivo
Quando você ouve “fechar arquivo”, é fácil imaginar que é uma operação manual obrigatória, como “colocar ponto e vírgula”. Mas em C++ os streams de arquivo são feitos para fecharem automaticamente: quando o objeto do stream sai do escopo, seu destrutor é chamado e o arquivo é fechado.
Isso é RAII puro: Resource Acquisition Is Initialization — o recurso é adquirido na criação do objeto e liberado quando o objeto é destruído.
Imagine que o stream é um “funcionário” a quem você deu a chave da sala (arquivo). Enquanto o funcionário trabalha (o objeto vive), a chave está com ele. Assim que o funcionário vai embora (o objeto sai do scope), a chave é entregue à segurança automaticamente. Você não precisa sair correndo e gritar “Devolvam as chaves!”.
Esquematicamente isso fica assim:
flowchart TD
A["Criado std::ofstream out('tasks.txt')"] --> B["Escrevemos dados out << ..."]
B --> C["Saímos do bloco { } ou da função"]
C --> D["O destrutor de out é chamado automaticamente"]
D --> E["Arquivo fechado corretamente (RAII)"]
E sim: mesmo que você saia da função via return, o RAII ainda age. Por isso RAII é um dos maiores “antiestresse” do C++.
Então precisamos de close()?
Após dizer “fecha sozinho” surge a pergunta: “Então por que existe close()?” Ele existe porque às vezes é necessário fechar o arquivo antes do fim da função ou do bloco. Por exemplo, você quer gravar um arquivo, fechá‑lo, e depois abrir outro arquivo com o mesmo objeto de stream — ou reabrir o mesmo arquivo em outro modo.
Regra simples para iniciantes: se você não consegue explicar imediatamente por que precisa de close(), provavelmente não precisa dele.
Exemplo onde close() faz sentido: reabrir o stream para outro arquivo.
#include <fstream>
#include <iostream>
int main() {
std::ofstream out("a.txt");
if (!out) return 1;
out << "A\n";
out.close(); // fechou explicitamente
out.open("b.txt");
out << "B\n";
}
Mas se você só escreve em um arquivo e sai da função — não complique: RAII já faz tudo certo.
5. Mini‑prática: salvando tarefas em arquivo
Para não ficar tudo teórico, vamos continuar a ideia de exemplo: um pequeno app de console “lista de tarefas”. Suponha que já temos o modelo Task e uma lista std::vector<Task>, e que os comandos de adicionar/exibir foram feitos antes. Agora vamos adicionar a salvação.
Nesta aula vamos focar não no formato ou parsing, mas na disciplina: abrir → checar → escrever → sair da função (e fechar automaticamente).
Modelo da tarefa
#include <string>
struct Task {
int id{};
bool done{};
std::string title;
};
Função de salvar save_tasks
#include <fstream>
#include <string>
#include <vector>
bool save_tasks(const std::string& filename, const std::vector<Task>& tasks) {
std::ofstream out(filename);
if (!out) return false;
for (const Task& t : tasks) {
out << t.id << ' ' << t.done << ' ' << t.title << '\n';
}
return true;
}
Observe: não chamamos out.close() em nenhum lugar. Não precisamos. Assim que a função terminar, out desaparecerá e o arquivo será fechado.
Uso em main()
#include <iostream>
#include <vector>
int main() {
std::vector<Task> tasks = { {1, false, "Buy milk"}, {2, true, "Learn C++"} };
if (!save_tasks("tasks.txt", tasks)) {
std::cerr << "Falha ao salvar\n";
return 1;
}
std::cout << "Salvo!\n"; // Saved!
}
Isso já é um passo útil: agora o programa pode deixar uma marca no mundo externo.
6. Diretório de trabalho: onde o arquivo fica
Quando você abre "tasks.txt", quase sempre usa um caminho relativo. A pergunta “onde exatamente será criado esse arquivo?” vira um pequeno enigma: depende de qual é a diretoria de trabalho a partir da qual o programa é executado. Na Web‑IDE é uma coisa, na IDE é outra, e ao executar no terminal é outra ainda.
Portanto, ao depurar é útil lembrar: se o arquivo “não aparece”, muitas vezes o problema não é o std::ofstream, e sim o caminho. Nos estágios iniciais é mais simples usar arquivos próximos ao executável do projeto (conforme o ambiente) e imprimir uma mensagem clara caso a abertura falhe. Depois, quando houver bibliotecas de manipulação de caminhos, isso fica mais fácil — hoje o foco é streams e RAII.
7. std::fstream: stream para leitura e escrita
Às vezes você quer “ler e escrever no mesmo arquivo”. Para isso existe std::fstream. Mas, como um canivete suíço, ele é ótimo até o momento em que você tenta usar uma ferramenta errada para outra tarefa. Ou seja: use quando for apropriado.
Por ora é suficiente saber que fstream existe e pode ser aberto para leitura e escrita. Nesta aula não vamos discutir modos de abertura, apenas mostrar um exemplo mínimo de criação.
#include <fstream>
#include <iostream>
int main() {
std::fstream io("tasks.txt"); // por padrão o comportamento depende da implementação e do modo
if (!io) {
std::cerr << "Não é possível abrir tasks.txt\n";
return 1;
}
}
No código real costuma‑se abrir fstream com modos explícitos para não ficar em dúvida sobre o que acontece com o arquivo.
8. Erros típicos
Erro nº 1: “Abri o arquivo, então ele abriu”.
Um iniciante frequentemente escreve std::ifstream in("data.txt"); e já faz a leitura sem checar o estado do stream. Se o arquivo não abriu, a leitura simplesmente não vai acontecer e o código parecerá “funcionar”. Remédio: logo após abrir, faça if(!in) { ... } e imprima uma mensagem clara.
Erro nº 2: fechamento manual do arquivo “só por garantia” em todo lugar do código.
Às vezes, após conhecer close(), dá vontade de colocá‑lo em todo canto, como alho na comida “por precaução”. O resultado são saídas extras e em algum lugar você pode fechar o stream cedo demais e se surpreender por que a escrita não funciona. Se você não precisa fechar o arquivo antes do fim do scope, confie no RAII.
Erro nº 3: scope do stream demasiado amplo.
O stream é declarado no começo do main, usado numa pequena parte, e depois “fica pendurado” até o fim do programa. Isso torna o código menos claro e aumenta a chance de uso indevido. Bom hábito: manter o stream no escopo onde realmente é necessário — às vezes até num bloco { ... } separado.
Erro nº 4: confundir os papéis de ifstream e ofstream.
Na pressa é fácil declarar std::ifstream out("file.txt") e depois estranhar erros ou falta de resultado. O compilador nem sempre “entende” sua intenção. Nomeie variáveis de forma honesta (in, out, io) e escolha o tipo de stream conforme a tarefa: leitura — ifstream, escrita — ofstream, ambos — fstream.
Erro nº 5: esperar que o arquivo esteja sempre ao lado e ignorar o diretório de trabalho.
Muito comum: “o arquivo existe no projeto, mas o programa diz que não pode abrir”. Frequentemente a razão é o diretório atual incorreto ao executar. Nessa fase basta lembrar: caminhos relativos dependem de onde você executou o programa. Assim, falha ao abrir é um cenário normal, não motivo para “quebrar tudo sem avisar”.
GO TO FULL VERSION