Manipulação de arquivos em modo texto com C++
Durante a execução de um programa, os dados armazenados em variáveis, vetores e objetos permanecem normalmente na memória principal do computador. Quando o programa é encerrado, esse conteúdo é perdido. A manipulação de arquivos permite que determinadas informações sejam transferidas da memória para um dispositivo de armazenamento, como SSD ou disco rígido, possibilitando sua recuperação em execuções posteriores.
Em C++, a entrada e a saída de dados são organizadas por meio do conceito de stream, ou fluxo. Um fluxo representa uma sequência de dados que pode ser enviada a um destino ou recebida de uma origem. O teclado, o monitor, um arquivo e uma cadeia de caracteres podem ser tratados como fluxos por meio de abstrações semelhantes (Stroustrup, 2013).
A biblioteca-padrão disponibiliza as classes std::ifstream, std::ofstream e std::fstream para manipulação de arquivos. A classe ifstream representa um fluxo de entrada associado a um arquivo, enquanto ofstream representa um fluxo de saída (CPPreference, 2026a). A classe fstream reúne as duas operações, permitindo leitura e escrita no mesmo fluxo. Essas classes são declaradas no cabeçalho <fstream>.
Neste artigo, estamos continuando a revisão do conteúdo iniciado no texto intitulado Revisando Algoritmos e Técnicas de Programação. Para isso, vamos evoluir um único sistema implementado em C++ que tem o seguinte contexto:
- A ideia é desenvolver um backlog de filmes, ou seja, uma lista de filmes para assistir e avaliar.
- Os exemplos funcionam a partir do C++ 17.
- Os nomes de variáveis e funções estão em Português do Brasil, enquanto palavras reservadas e identificadores permanecem em inglês.
Vamos começar o programa importando as bibliotecas necessárias e declarando os protótipos das funções e procedimentos que iremos construir. A ideia é que o sistema seja modular e capaz de cadastrar, listar, pesquisar, avaliar e persistir filmes em arquivos texto.
#include <cctype>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <limits>
#include <sstream>
#include <string>
using namespace std;
const int CAPACIDADE_MAXIMA = 100;
const double NOTA_MINIMA = 0.0;
const double NOTA_MAXIMA = 10.0;
const string NOME_ARQUIVO = "filmes.txt";
struct Filme {
int codigo;
string titulo;
int ano;
double nota;
bool assistido;
};
// Protótipos das funções e procedimentos
void limparEntrada();
void exibirMenu();
int lerInteiro(const string& mensagem);
double lerNota(const string& mensagem);
bool lerBooleano(const string& mensagem);
string converterParaMinusculas(string texto);
string obterSituacao(bool assistido);
void cadastrarFilme(Filme backlog[], int& quantidade);
void listarFilmes(const Filme backlog[], int quantidade);
int buscarIndicePorCodigo(const Filme backlog[], int quantidade, int codigo);
void pesquisarPorTitulo(const Filme backlog[], int quantidade);
void atualizarNota(Filme backlog[], int quantidade);
void marcarComoAssistido(Filme backlog[], int quantidade);
bool salvarTexto(const Filme backlog[], int quantidade, const string& nomeArquivo);
bool carregarTexto(Filme backlog[], int& quantidade, const string& nomeArquivo);A partir daqui, daremos uma pausa no exemplo para discutir os fundamentos de leitura e gravação em arquivos com modo texto.
Arquivos e persistência de dados
Persistência é a capacidade de conservar dados além do tempo de execução do processo que os criou. Em um programa sem persistência, os registros existem somente enquanto o programa permanece em execução. Ao salvar esses registros em um arquivo, o estado da aplicação pode ser reconstruído posteriormente.
No backlog de filmes, cada registro possui os seguintes atributos declarados como uma struct:
struct Filme {
int codigo;
string titulo;
int ano;
double nota;
bool assistido;
};Enquanto o programa estiver em execução, os filmes serão armazenados em um vetor:
Filme backlog[CAPACIDADE_MAXIMA];Como esse vetor reside na memória do processo, criaremos a função salvarTexto para converter os valores armazenados no vetor em caracteres e gravá-los no arquivo filmes.txt. Concomitantemente, teremos a função carregarTexto para realizar o caminho inverso: ler os caracteres, interpretar os campos e reconstruir os objetos do tipo Filme.
Esse processo pode ser compreendido como uma forma simples de serialização. Serializar significa converter uma estrutura mantida em memória para uma representação adequada ao armazenamento ou à transmissão. O caminho oposto, chamado desserialização, reconstrói a estrutura original a partir dessa representação.
A biblioteca <fstream>
Para utilizar arquivos em C++, deve-se incluir o cabeçalho fstream que oferece três classes principais:
#include <fstream>A classe ofstream, abreviação de output file stream, é usada principalmente para escrita:
ofstream arquivo("filmes.txt");Quando o construtor recebe apenas o nome do arquivo, o fluxo é aberto para saída. Caso o arquivo não exista, normalmente ele será criado. Se já existir, seu conteúdo anterior será descartado porque a abertura padrão de um ofstream inclui o comportamento de saída com truncamento, salvo quando outro modo for explicitamente indicado.
A segunda opção é a classe ifstream – abreviação de input file stream – que é usada para leitura:
ifstream arquivo("filmes.txt");Nela, o construtor tenta localizar e abrir o arquivo informado. Quando a operação falha, o fluxo registra um estado de erro. A documentação da biblioteca descreve que a abertura associa o arquivo ao fluxo e define failbit quando não consegue realizar a operação (CPPreference, 2026a).
A última classe é a fstream, que permite realizar entrada e saída no mesmo arquivo e pode ser escrita assim:
fstream arquivo(
"filmes.txt",
ios::in | ios::out
);Essa alternativa é adequada quando o mesmo fluxo precisa ler e escrever. Em programas introdutórios, separar responsabilidades por meio de ifstream e ofstream costuma tornar o código mais claro.
Abertura e verificação do arquivo
A criação do objeto de fluxo não garante que o arquivo tenha sido aberto corretamente. O programa deve verificar o resultado antes de executar operações de entrada ou saída, conforme abaixo:
ofstream arquivo(nomeArquivo);
if (!arquivo.is_open()) {
return false;
}A função is_open() informa se o fluxo possui um arquivo associado. Essa verificação pode detectar situações como nome ou caminho inválido, ausência de permissões, diretório inexistente ou indisponibilidade do dispositivo de armazenamento.
A mesma lógica é utilizada para leitura:
ifstream arquivo(nomeArquivo);
if (!arquivo.is_open()) {
return false;
}Outra possibilidade é verificar diretamente o estado lógico do fluxo:
if (!arquivo) {
return false;
}As duas formas são válidas, embora is_open() expresse especificamente a intenção de verificar a abertura.
Modos de abertura
Os modos de abertura são definidos pelo tipo ios_base::openmode (CPPreference, 2026b). Eles podem ser combinados por meio do operador binário ( | ). A biblioteca define, entre outras possibilidades, os modos:
ios::in– abre o fluxo para leitura. Para a classeifstream, esse modo já é aplicado por padrão:
ifstream arquivo(
"filmes.txt",
ios::in
);ios::out– abre o fluxo para escrita. Para a classeofstream, esse modo também é implícito:
ofstream arquivo(
"filmes.txt",
ios::out
);ios::trunc– descarta o conteúdo existente quando o arquivo é aberto. Essa combinação é adequada quando o arquivo deve representar integralmente o estado atual do programa:
ofstream arquivo(
"filmes.txt",
ios::out | ios::trunc
);ios::app– mantém o conteúdo existente e adiciona cada nova escrita ao final. Esse modo é apropriado para logs, históricos e registros acumulativos.
ofstream arquivo(
"historico.txt",
ios::app
);ios::ate– abre o arquivo e posiciona inicialmente o indicador no final:
fstream arquivo(
"filmes.txt",
ios::in | ios::out | ios::ate
);Ao contrário do modo app, o modo ate permite reposicionar o fluxo depois da abertura. No modo app, cada operação de escrita é direcionada ao final do arquivo.
Escrita em arquivos de texto
A escrita em ofstream utiliza o operador de inserção ( << ), semelhante ao empregado com cout:
arquivo << "Backlog de filmes\n";
arquivo << 10 << '\n';
arquivo << 8.5 << '\n';Os valores numéricos são convertidos para uma representação textual. O inteiro 10, por exemplo, é armazenado como os caracteres '1' e '0', e não como a representação binária interna do tipo int.
Na função salvarTexto que demonstraremos ao final, a primeira linha após a verificação de abertura do arquivo armazena a quantidade de filmes:
arquivo << quantidade << '\n';Em seguida, cada filme será gravado em uma linha, persistindo o registro passado pelo usuário:
arquivo << filme.codigo << '|'
<< filme.titulo << '|'
<< filme.ano << '|'
<< filme.nota << '|'
<< filme.assistido << '\n';Em nosso código final, o caractere ( | ) atuará como delimitador, separando os diferentes campos do registro.
Um arquivo gerado pelo programa final pode apresentar como resultado o seguinte conteúdo no terminal:
3
1|Interestelar|2014|9.5|1
2|Blade Runner 2049|2017|8.7|1
3|Duna: Parte Dois|2024|9.1|0
A primeira linha informa que existem três registros. Cada linha posterior apresentará código, título, ano, nota e situação.
Precisão de números reais
Os números do tipo double são armazenados internamente por meio de uma representação binária de ponto flutuante. Ao convertê-los para texto, utilizar poucas casas ou poucos dígitos pode impedir a reconstrução exata do valor original.
Por esse motivo, o código utilizará a seguinte configuração:
arquivo << setprecision(17);O manipulador setprecision, declarado em <iomanip>, define a quantidade de dígitos significativos utilizados na saída. O valor 17 é suficiente para preservar a capacidade de ida e volta de grande parte dos valores representáveis em um double segundo o padrão IEEE 754 (ISO, 2017).
Essa configuração é diferente de:
arquivo << fixed << setprecision(2);Com fixed, a precisão passa a indicar a quantidade de casas decimais. Essa forma é apropriada para apresentação ao usuário, mas pode provocar arredondamento quando empregada como formato de persistência.
Leitura de arquivos de texto
A função getline permite ler uma linha completa:
string linha;
if (!getline(arquivo, linha)) {
return false;
}A leitura termina quando encontra o caractere de nova linha ou quando alcança o final do arquivo. A versão de getline que recebe um delimitador permite interromper a leitura em outro caractere específico. A documentação da biblioteca descreve que o delimitador é extraído do fluxo, mas não é acrescentado ao conteúdo armazenado.
Separação dos campos com stringstream
Cada linha de filme contém vários campos separados pelo símbolo ( | ). Para interpretar essa linha, no programa criaremos um stringstream:
stringstream leitor(linha);Um stringstream permite tratar uma string como um fluxo. Assim, pode-se usar getline várias vezes, indicando o caractere delimitador:
getline(leitor, campo, '|');A reconstrução do registro de um filme seguirá a sequência abaixo:
if (!getline(leitor, campo, '|')) {
return false;
}
filme.codigo = stoi(campo);
if (!getline(leitor, filme.titulo, '|')) {
return false;
}
if (!getline(leitor, campo, '|')) {
return false;
}
filme.ano = stoi(campo);
if (!getline(leitor, campo, '|')) {
return false;
}
filme.nota = stod(campo);
if (!getline(leitor, campo, '|')) {
return false;
}
filme.assistido = stoi(campo) != 0;A função stoi na linha 4 converte uma string para int. Caso o conteúdo não represente um número inteiro válido, uma exceção será lançada. De objetivo similar, mas com foco de conversão diferente, a função stod na linha 18 converte o texto para o tipo double.
Leitura até o final do arquivo
Um padrão frequente de leitura é utilizar getline como condição de repetição:
while (getline(arquivo, linha)) {
// processar a linha
}Esse padrão é preferível ao método eof():
while (!arquivo.eof()) {
getline(arquivo, linha);
}O método eof() somente indica o fim do arquivo depois que uma operação de leitura tenta ultrapassá-lo. Utilizá-lo como condição principal pode ocasionar uma iteração adicional ou o processamento indevido de dados antigos.
Fechamento do arquivo
Um arquivo pode ser fechado explicitamente:
arquivo.close();Entretanto, isso normalmente não é necessário quando o objeto de fluxo é local. Ao sair do escopo, seu destrutor fecha automaticamente o arquivo. Esse comportamento decorre do princípio RAII (do inglês, Resource Acquisition Is Initialization), amplamente utilizado em C++ para associar a duração de um recurso à duração de um objeto (Stroustrup, 2013).
O fechamento explícito pode ser útil quando o arquivo deve ser liberado antes do término da função ou quando o mesmo objeto de fluxo será associado a outro arquivo.
Quando utilizar arquivos de texto
Arquivos de texto são indicados quando os dados precisam ser lidos ou modificados por pessoas, quando a facilidade de depuração é importante ou quando outras ferramentas devem consumir o conteúdo.
São exemplos apropriados:
- arquivos de configuração;
- arquivos CSV;
- documentos JSON ou XML;
- logs;
- exportação de relatórios;
- pequenos cadastros educacionais;
- intercâmbio de dados entre linguagens;
- dados que precisam ser versionados com Git.
O backlog de filmes utiliza modo texto porque a quantidade de registros é pequena, o formato é simples e a inspeção do arquivo facilita o aprendizado.
Continuação do código
Concietos discutidos, agora vamos dar continuidade ao mini projeto. Começamos com a construção da função main, que representa o bloco principal de execução em C++. Em seguida, implementamos as funções e procedimentos dos protótipos que havíamos declarado.
int main() {
Filme backlog[CAPACIDADE_MAXIMA];
int quantidade = 0;
int opcao;
do {
exibirMenu();
opcao = lerInteiro("Opção: ");
switch (opcao) {
case 1:
cadastrarFilme(backlog, quantidade);
break;
case 2:
listarFilmes(backlog, quantidade);
break;
case 3:
pesquisarPorTitulo(backlog, quantidade);
break;
case 4:
atualizarNota(backlog, quantidade);
break;
case 5:
marcarComoAssistido(
backlog,
quantidade
);
break;
case 6:
if (salvarTexto(backlog, quantidade, NOME_ARQUIVO)) {
cout << "Dados salvos em " << NOME_ARQUIVO << ".\n";
} else {
cout << "Falha ao salvar " << "o arquivo texto.\n";
}
break;
case 7:
if (carregarTexto(backlog, quantidade, NOME_ARQUIVO)) {
cout << "Dados carregados de " << NOME_ARQUIVO << ".\n";
} else {
cout << "Falha ao carregar " << "o arquivo texto.\n";
}
break;
case 0:
cout << "Programa encerrado.\n";
break;
default:
cout << "Opção inválida.\n";
}
} while (opcao != 0);
return 0;
}
void limparEntrada() {
cin.clear();
cin.ignore(
numeric_limits<streamsize>::max(),
'\n'
);
}
void exibirMenu() {
cout << "\n===== BACKLOG DE FILMES =====\n";
cout << "1 - Cadastrar filme\n";
cout << "2 - Listar filmes\n";
cout << "3 - Pesquisar por título\n";
cout << "4 - Atualizar nota\n";
cout << "5 - Marcar como assistido\n";
cout << "6 - Salvar em texto\n";
cout << "7 - Carregar de texto\n";
cout << "0 - Sair\n";
}
int lerInteiro(const string& mensagem) {
int valor;
while (true) {
cout << mensagem;
if (cin >> valor) {
limparEntrada();
return valor;
}
cout << "Valor inválido. "
<< "Digite um número inteiro.\n";
limparEntrada();
}
}
double lerNota(const string& mensagem) {
double nota;
while (true) {
cout << mensagem;
if (cin >> nota &&
nota >= NOTA_MINIMA &&
nota <= NOTA_MAXIMA) {
limparEntrada();
return nota;
}
cout << "Nota inválida. Informe um valor entre "
<< NOTA_MINIMA << " e "
<< NOTA_MAXIMA << ".\n";
limparEntrada();
}
}
bool lerBooleano(const string& mensagem) {
while (true) {
int valor = lerInteiro(mensagem);
if (valor == 0 || valor == 1) {
return valor == 1;
}
cout << "Digite 1 para sim ou 0 para não.\n";
}
}
string converterParaMinusculas(string texto) {
for (char& caractere : texto) {
caractere = static_cast<char>(
tolower(
static_cast<unsigned char>(
caractere
)
)
);
}
return texto;
}
string obterSituacao(bool assistido) {
return assistido
? "Assistido"
: "Pendente";
}
void cadastrarFilme(Filme backlog[], int& quantidade) {
if (quantidade >= CAPACIDADE_MAXIMA) {
cout << "Capacidade máxima atingida.\n";
return;
}
Filme novoFilme;
novoFilme.codigo =
quantidade == 0
? 1
: backlog[quantidade - 1].codigo + 1;
cout << "Título: ";
getline(cin, novoFilme.titulo);
while (novoFilme.titulo.empty()) {
cout << "O título não pode ficar vazio.\n";
cout << "Título: ";
getline(cin, novoFilme.titulo);
}
novoFilme.ano =
lerInteiro("Ano de lançamento: ");
novoFilme.nota =
lerNota("Nota de 0 a 10: ");
novoFilme.assistido =
lerBooleano("Assistido? (1/0): ");
backlog[quantidade] = novoFilme;
quantidade++;
cout << "Filme cadastrado com o código "
<< novoFilme.codigo << ".\n";
}
void listarFilmes(const Filme backlog[], int quantidade) {
if (quantidade == 0) {
cout << "O backlog está vazio.\n";
return;
}
cout << left
<< setw(8) << "Código"
<< setw(32) << "Título"
<< setw(8) << "Ano"
<< setw(8) << "Nota"
<< "Situação\n";
cout << string(70, '-') << '\n';
for (int indice = 0; indice < quantidade; indice++) {
const Filme& filme = backlog[indice];
cout << left
<< setw(8) << filme.codigo
<< setw(32)
<< filme.titulo.substr(0, 30)
<< setw(8) << filme.ano
<< setw(8)
<< fixed
<< setprecision(1)
<< filme.nota
<< obterSituacao(filme.assistido)
<< '\n';
}
}
int buscarIndicePorCodigo(const Filme backlog[], int quantidade, int codigo) {
for (int indice = 0; indice < quantidade; indice++) {
if (backlog[indice].codigo == codigo) {
return indice;
}
}
return -1;
}
void pesquisarPorTitulo(const Filme backlog[], int quantidade) {
if (quantidade == 0) {
cout << "O backlog está vazio.\n";
return;
}
string termo;
cout << "Informe parte do título: ";
getline(cin, termo);
string termoNormalizado = converterParaMinusculas(termo);
bool encontrou = false;
for (int indice = 0; indice < quantidade; indice++) {
string tituloNormalizado = converterParaMinusculas(backlog[indice].titulo);
if (tituloNormalizado.find(termoNormalizado) != string::npos) {
const Filme& filme = backlog[indice];
cout << filme.codigo << " - "
<< filme.titulo << " ("
<< filme.ano << "), nota "
<< filme.nota << ", "
<< obterSituacao(filme.assistido)
<< '\n';
encontrou = true;
}
}
if (!encontrou) {
cout << "Nenhum filme encontrado.\n";
}
}
void atualizarNota(Filme backlog[], int quantidade) {
int codigo = lerInteiro("Código do filme: ");
int indice = buscarIndicePorCodigo(backlog, quantidade, codigo);
if (indice == -1) {
cout << "Filme não encontrado.\n";
return;
}
backlog[indice].nota = lerNota("Nova nota: ");
cout << "Nota atualizada.\n";
}
void marcarComoAssistido(Filme backlog[], int quantidade) {
int codigo = lerInteiro("Código do filme: ");
int indice = buscarIndicePorCodigo(backlog, quantidade, codigo);
if (indice == -1) {
cout << "Filme não encontrado.\n";
return;
}
backlog[indice].assistido = true;
cout << "Filme marcado como assistido.\n";
}
bool salvarTexto(const Filme backlog[], int quantidade, const string& nomeArquivo) {
ofstream arquivo(nomeArquivo);
if (!arquivo.is_open()) {
return false;
}
arquivo << quantidade << '\n';
arquivo << setprecision(17);
for (int indice = 0; indice < quantidade; indice++) {
const Filme& filme = backlog[indice];
arquivo << filme.codigo << '|'
<< filme.titulo << '|'
<< filme.ano << '|'
<< filme.nota << '|'
<< filme.assistido << '\n';
}
return static_cast<bool>(arquivo);
}
bool carregarTexto(Filme backlog[], int& quantidade, const string& nomeArquivo) {
ifstream arquivo(nomeArquivo);
if (!arquivo.is_open()) {
return false;
}
string linha;
if (!getline(arquivo, linha)) {
return false;
}
int quantidadeArquivo;
try {
quantidadeArquivo = stoi(linha);
} catch (...) {
return false;
}
if (quantidadeArquivo < 0 ||
quantidadeArquivo > CAPACIDADE_MAXIMA) {
return false;
}
Filme temporario[CAPACIDADE_MAXIMA];
int carregados = 0;
while (carregados < quantidadeArquivo && getline(arquivo, linha)) {
stringstream leitor(linha);
string campo;
Filme filme;
try {
if (!getline(leitor, campo, '|')) {
return false;
}
filme.codigo = stoi(campo);
if (!getline(leitor, filme.titulo, '|')) {
return false;
}
if (!getline(leitor, campo, '|')) {
return false;
}
filme.ano = stoi(campo);
if (!getline(leitor, campo, '|')) {
return false;
}
filme.nota = stod(campo);
if (!getline(leitor, campo, '|')) {
return false;
}
filme.assistido = stoi(campo) != 0;
} catch (...) {
return false;
}
temporario[carregados] = filme;
carregados++;
}
if (carregados != quantidadeArquivo) {
return false;
}
for (int indice = 0; indice < carregados; indice++) {
backlog[indice] = temporario[indice];
}
quantidade = carregados;
return true;
}Para um exemplo pedagógico, o delimitador simples ( | ) torna o algoritmo mais compreensível. Contudo, em uma aplicação de produção, o formato deve considerar os caracteres especiais, codificação, versões e validação do esquema.
Compilação em C++17
No GCC ou no Clang, o programa pode ser compilado no terminal com o comando abaixo:
g++ -std=c++17 -Wall -Wextra -pedantic main.cpp -o backlog
A opção -std=c++17 seleciona o padrão C++17. As opções -Wall, -Wextra e -pedantic habilitam verificações adicionais que ajudam a identificar construções problemáticas.
A execução em Linux pode ser feita com:
./backlog
enquanto no Windows basta procurar o arquivo:
backlog.exe
Você também pode compilar o código na sua IDE preferida e ver o resultado diretamente no terminal interno. Nesse caso, fique a vontade para escolher.
Conclusão
A manipulação de arquivos em modo texto constitui uma introdução importante à persistência de dados em C++. Ela conecta os conceitos de memória, estruturas de dados, fluxos, serialização e tratamento de erros em um cenário prático.
As classes ifstream e ofstream oferecem uma interface semelhante a cin e cout, reduzindo a quantidade de novos conceitos necessários para iniciar a leitura e a escrita de arquivos. Entretanto, a simplicidade da interface não elimina a necessidade de projetar corretamente o formato, validar os dados recebidos e tratar possíveis falhas de entrada e saída.
Obrigado pela leitura e bons estudos!
Referências
CPPreference. std::basic_ifstream. 2026a. Disponível em: <https://en.cppreference.com/cpp/io/basic_ifstream>. Acesso em: 30 jul. 2026.
CPPreference. std::ios_base::openmode. 2026b. Disponível em: <https://en.cppreference.com/cpp/io/ios_base/openmode>. Acesso em: 30 jul. 2026.
ISO. ISO/IEC 14882:2017: programming languages — C++. Geneva: International Organization for Standardization, 2017. Disponível em: <https://www.iso.org/standard/68564.html>. Acesso em: 30 jul. 2026.
STROUSTRUP, Bjarne. The C++ programming language. 4. ed. Upper Saddle River: Addison-Wesley, 2013.


