C++ Ler e Escrever Arquivos

Tudo o que um programa guarda em variáveis some quando ele termina. A lista de compras, as notas da turma, o placar do jogo: fechou o programa, perdeu. Para guardar de verdade, o programa precisa escrever num arquivo, e da próxima vez, ler o arquivo. Em C++ isso é feito com a biblioteca <fstream>, e a boa notícia é que você já sabe usá-la: ela funciona igualzinho ao cout e ao cin.

Você vai aprender
  • Como gravar texto num arquivo com std::ofstream e <<.
  • A diferença entre sobrescrever e acrescentar (std::ios::app).
  • Como ler um arquivo linha a linha com std::getline, e palavra a palavra ou número a número com >>.
  • Como conferir se o arquivo abriu (if (!arq)), e o que acontece quando você não confere.
  • Quando o arquivo é fechado, e a armadilha de ler um arquivo que ainda está aberto para escrita.
  • Como ler a planilha alunos.csv da Ana, separando os campos pela vírgula.
  • O básico de std::filesystem: o arquivo existe? Qual o tamanho?

Todos os programas desta aula foram compilados com o g++ 15 na máquina fictícia da Ana, na pasta ~/cpp/arquivos, com o comando padrão do guia: g++ -std=c++23 -Wall -Wextra arquivo.cpp -o programa. Para ver o conteúdo dos arquivos, usamos o comando cat do terminal. No editor do guia, os arquivos de dados aparecem como abas ao lado do código.


A ideia: o arquivo é outro "terminal"

Você já conhece dois "canos" de texto: o std::cout, que leva texto do programa para a tela, e o std::cin, que traz texto do teclado para o programa. Em C++, esses canos se chamam streams ("fluxos"). Um arquivo é só mais um cano, com a ponta ligada no disco em vez da tela ou do teclado:

DireçãoTela / tecladoArquivoComo usar
Programa → fora (escrever)std::coutstd::ofstream<<
Fora → programa (ler)std::cinstd::ifstream>> e std::getline

Os nomes se leem assim: ofstream = output file stream, "fluxo de saída para arquivo"; ifstream = input file stream, "fluxo de entrada de arquivo". Os dois moram no cabeçalho <fstream>.


1. Escrever: std::ofstream

#include <fstream>
#include <iostream>

int main() {
    std::ofstream arq("compras.txt");
    arq << "arroz\n";
    arq << "feijão\n";
    arq << "café " << 2 << " pacotes\n";
    std::cout << "Lista gravada.\n";
}
#include <fstream>
Traz o ofstream e o ifstream.
std::ofstream arq("compras.txt");
Cria uma variável arq ligada ao arquivo compras.txt, aberto para escrita. Se o arquivo não existe, ele é criado; se existe, é esvaziado. O nome é procurado na pasta em que o programa está rodando.
arq << "arroz\n";
Escreve no arquivo, exatamente como o cout escreveria na tela. O \n pula a linha dentro do arquivo.
arq << "café " << 2 << " pacotes\n";
Números, variáveis e encadeamentos funcionam igual ao cout.
}
No fim do main, o arq deixa de existir e o arquivo é fechado sozinho (seção 4).
$ g++ -std=c++23 -Wall -Wextra escrever.cpp -o escrever
$ ./escrever
Lista gravada.                « só esta linha foi para a tela
$ cat compras.txt
arroz
feijão
café 2 pacotes
$ ./escrever
Lista gravada.
$ cat compras.txt
arroz                         « rodou duas vezes, mas a lista não duplicou
feijão
café 2 pacotes

Repare na segunda execução: o ofstream apagou o conteúdo antigo e escreveu tudo de novo. Esse é o comportamento padrão, chamado sobrescrever. Cuidado: abrir um arquivo importante com ofstream por engano apaga o que estava nele.

Acrescentar no fim: std::ios::app

Para um diário, um registro de vendas ou um placar, você quer o contrário: manter o que já existe e acrescentar no fim. Para isso, passe um segundo argumento, std::ios::app (de append, "anexar"):

#include <fstream>
#include <iostream>
#include <string>

int main() {
    std::string texto;
    std::cout << "O que você fez hoje? ";
    std::getline(std::cin, texto);

    std::ofstream arq("diario.txt", std::ios::app);
    arq << "- " << texto << '\n';
    std::cout << "Anotado no diário.\n";
}
$ g++ -std=c++23 -Wall -Wextra diario.cpp -o diario
$ echo "Estudei C++" | ./diario
O que você fez hoje? Anotado no diário.
$ echo "Fiz um bolo" | ./diario
O que você fez hoje? Anotado no diário.
$ cat diario.txt
- Estudei C++
- Fiz um bolo                 « a segunda execução acrescentou, não apagou

O echo "…" | entrega o texto ao programa como se você o digitasse (por isso a resposta não aparece na tela). Se o diario.txt não existir, o std::ios::app o cria, como o modo normal.

AberturaArquivo não existeArquivo já existe
std::ofstream arq("x.txt");CriaApaga tudo e escreve do zero
std::ofstream arq("x.txt", std::ios::app);CriaMantém e escreve no fim

2. Ler: std::ifstream

Linha a linha, com std::getline

A forma mais comum de ler um arquivo de texto é uma linha por vez, com o mesmo std::getline que você usou com o teclado (veja Ler uma Linha):

#include <fstream>
#include <iostream>
#include <string>

int main() {
    std::ifstream arq("compras.txt");
    std::string linha;
    int numero = 1;
    while (std::getline(arq, linha)) {
        std::cout << numero << ": " << linha << '\n';
        ++numero;
    }
}
$ g++ -std=c++23 -Wall -Wextra ler.cpp -o ler
$ ./ler
1: arroz
2: feijão
3: café 2 pacotes
std::ifstream arq("compras.txt");
Abre o arquivo para leitura. O arquivo não é alterado.
std::getline(arq, linha)
Lê uma linha inteira do arquivo (sem o \n do fim) e guarda em linha. Igual ao std::getline(std::cin, linha), trocando o teclado pelo arquivo.
while (std::getline(arq, linha))
O getline devolve o próprio arq, que vale "verdadeiro" enquanto a leitura dá certo. Quando o arquivo acaba, a leitura falha e o laço para. Essa é a forma correta de ler "até o fim".
Não use while (!arq.eof())

Você vai ver em código antigo um laço while (!arq.eof()) (end of file, "fim do arquivo") com o getline dentro. O mesmo ler.cpp escrito assim (eof.cpp) mostra uma linha a mais, vazia:

$ ./eof
1: arroz
2: feijão
3: café 2 pacotes
4:                   « linha fantasma

O eof() só fica verdadeiro depois que uma leitura bate no fim do arquivo. Na volta 4, o getline falhou, mas o corpo do laço rodou assim mesmo. Coloque a própria leitura na condição do while, como no ler.cpp.

Missão: o arquivo compras.txt está na outra aba. Complete o programa para ler o arquivo linha a linha com std::getline e contar quantos itens a lista tem. A saída deve ser A lista tem 5 itens.

#include <fstream>
#include <iostream>
#include <string>

int main() {
    std::ifstream arq("compras.txt");
    if (!arq) {
        std::cerr << "Erro: não consegui abrir compras.txt\n";
        return 1;
    }
    std::string linha;
    int itens = 0;
    // leia o arquivo linha a linha, contando os itens

    std::cout << "A lista tem " << itens << " itens\n";
}
arroz
feijão
café
leite
banana
#include <fstream>
#include <iostream>
#include <string>

int main() {
    std::ifstream arq("compras.txt");
    if (!arq) {
        std::cerr << "Erro: não consegui abrir compras.txt\n";
        return 1;
    }
    std::string linha;
    int itens = 0;
    while (std::getline(arq, linha)) {
        ++itens;
    }
    std::cout << "A lista tem " << itens << " itens\n";
}

Palavra a palavra ou número a número, com >>

O >> funciona no arquivo como no cin (veja Entrada com cin): pula espaços e quebras de linha e lê um pedaço por vez. Com um std::string, cada pedaço é uma palavra:

#include <fstream>
#include <iostream>
#include <string>

int main() {
    std::ifstream arq("compras.txt");
    std::string palavra;
    int total = 0;
    while (arq >> palavra) {
        std::cout << '[' << palavra << ']';
        ++total;
    }
    std::cout << '\n' << total << " palavras\n";
}
$ g++ -std=c++23 -Wall -Wextra palavras.cpp -o palavras
$ ./palavras
[arroz][feijão][café][2][pacotes]
5 palavras

Com um double, cada pedaço é um número, e não importa como eles estão distribuídos nas linhas. O arquivo notas.txt tem duas notas na primeira linha e uma em cada uma das outras:

$ cat notas.txt
8.5 6.0
9.5
7
#include <fstream>
#include <iostream>

int main() {
    std::ifstream arq("notas.txt");
    double nota = 0.0;
    double soma = 0.0;
    int quantas = 0;
    while (arq >> nota) {
        soma += nota;
        ++quantas;
    }
    std::cout << quantas << " notas, média " << soma / quantas << '\n';
}
$ g++ -std=c++23 -Wall -Wextra numeros.cpp -o numeros
$ ./numeros
4 notas, média 7.75

O while (arq >> nota) para quando o arquivo acaba ou quando aparece algo que não é número. Se o notas.txt tivesse a palavra falta no meio, a leitura pararia ali, sem aviso.

Para ler…UseExemplo
Linhas inteiras (com espaços)std::getline(arq, linha)Lista de compras, frases, CSV
Palavras soltasarq >> palavraContar palavras de um texto
Números separados por espaço ou linhaarq >> numeroUma lista de notas ou medidas

3. O arquivo abriu? Sempre confira

Abrir um arquivo pode falhar: o nome está errado, o arquivo está em outra pasta, você não tem permissão. E o C++ não avisa sozinho. Veja o mesmo programa das notas, com o nome digitado errado (nota.txt em vez de notas.txt):

#include <fstream>
#include <iostream>

int main() {
    std::ifstream arq("nota.txt");
    double nota = 0.0;
    double soma = 0.0;
    int quantas = 0;
    while (arq >> nota) {
        soma += nota;
        ++quantas;
    }
    std::cout << quantas << " notas, média " << soma / quantas << '\n';
}
$ g++ -std=c++23 -Wall -Wextra semchecar.cpp -o semchecar
$ ./semchecar
0 notas, média -nan
$ echo $?
0                     « para o sistema, deu tudo certo

Nenhuma mensagem de erro. O ifstream não conseguiu abrir, a primeira leitura falhou, o laço não rodou nenhuma vez e a conta 0.0 / 0 deu -nan (not a number, "não é um número"; veja float e double). Num programa maior, esse silêncio vira um relatório errado que ninguém percebe.

A solução é testar a variável logo depois de abrir. if (!arq) se lê "se o arquivo não está bom":

#include <fstream>
#include <iostream>

int main() {
    std::ifstream arq("nota.txt");
    if (!arq) {
        std::cerr << "Erro: não consegui abrir nota.txt\n";
        return 1;
    }
    double nota = 0.0;
    double soma = 0.0;
    int quantas = 0;
    while (arq >> nota) {
        soma += nota;
        ++quantas;
    }
    std::cout << quantas << " notas, média " << soma / quantas << '\n';
}
$ g++ -std=c++23 -Wall -Wextra checar.cpp -o checar
$ ./checar
Erro: não consegui abrir nota.txt
$ echo $?
1                     « código de erro: quem chamou o programa fica sabendo
if (!arq)
Um stream "vale falso" quando está com problema: não abriu, ou a última leitura falhou. Também existe a forma por extenso, if (!arq.is_open()) ("não está aberto?").
std::cerr
A saída de erros (character error). Aparece na tela como o cout, mas é um cano separado: se alguém mandar a saída normal do programa para um arquivo, as mensagens de erro continuam na tela.
return 1;
Termina o main com o código 1, que no Linux quer dizer "deu errado" (o 0 é sucesso). Scripts e outros programas usam esse código para saber se você teve sucesso.

Escrever também pode falhar. O ofstream cria o arquivo, mas não cria pastas: se a pasta relatorios não existe, a abertura falha.

#include <fstream>
#include <iostream>

int main() {
    std::ofstream arq("relatorios/maio.txt");
    if (!arq) {
        std::cerr << "Erro: não consegui criar relatorios/maio.txt\n";
        return 1;
    }
    arq << "Vendas: 42\n";
}
$ ./gravarpasta
Erro: não consegui criar relatorios/maio.txt
$ echo $?
1

Regra: todo ifstream e todo ofstream são seguidos de um if (!arq). O C++ também pode avisar com uma exceção em vez de exigir o if; você vai conhecer exceções na próxima aula.


4. Fechar o arquivo

Você não precisa fechar o arquivo à mão. Quando a variável arq deixa de existir (no fim do bloco { } em que ela foi criada; veja Escopo), o arquivo é fechado sozinho. Esse jeito de amarrar um recurso a uma variável tem até nome, RAII, e você vai estudá-lo em Destrutores e RAII.

Existe o arq.close(), que fecha na hora. Ele é útil num caso específico: quando você escreveu e quer ler o mesmo arquivo na mesma função. Veja a armadilha:

#include <fstream>
#include <iostream>
#include <string>

int main() {
    std::ofstream saida("recado.txt");
    saida << "Volto às 15h\n";

    std::ifstream entrada("recado.txt");
    std::string linha;
    if (std::getline(entrada, linha)) {
        std::cout << "Li: " << linha << '\n';
    } else {
        std::cout << "O arquivo estava vazio!\n";
    }
}
$ g++ -std=c++23 -Wall -Wextra fechar.cpp -o fechar
$ ./fechar
O arquivo estava vazio!

Mas o programa escreveu "Volto às 15h"! O que aconteceu: para ser rápido, o ofstream junta o texto numa área da memória chamada buffer ("reservatório") e só manda tudo para o disco de vez em quando, ou quando é fechado. Na hora da leitura, o saida ainda estava aberto e o texto ainda estava no buffer. A correção é fechar o arquivo antes de ler. Um jeito elegante é colocar a escrita num bloco próprio:

#include <fstream>
#include <iostream>
#include <string>

int main() {
    {
        std::ofstream saida("recado.txt");
        saida << "Volto às 15h\n";
    }   // aqui o saida é fechado, e o texto vai para o disco

    std::ifstream entrada("recado.txt");
    std::string linha;
    if (std::getline(entrada, linha)) {
        std::cout << "Li: " << linha << '\n';
    } else {
        std::cout << "O arquivo estava vazio!\n";
    }
}
$ g++ -std=c++23 -Wall -Wextra fechar2.cpp -o fechar2
$ ./fechar2
Li: Volto às 15h

Escrever saida.close(); antes de abrir o entrada também resolve. As duas formas funcionam; o bloco tem a vantagem de que o saida nem existe mais depois, então não dá para usá-lo fechado por engano.

Experimente: este programa escreve e lê o mesmo arquivo. Tire as chaves do bloco (as duas linhas com { e } sozinhas) e rode de novo para ver a armadilha do buffer. Depois coloque saida.close(); antes do ifstream e confira que volta a funcionar.

#include <fstream>
#include <iostream>
#include <string>

int main() {
    {
        std::ofstream saida("recado.txt");
        saida << "Volto às 15h\n";
    }   // aqui o saida é fechado, e o texto vai para o disco

    std::ifstream entrada("recado.txt");
    std::string linha;
    if (std::getline(entrada, linha)) {
        std::cout << "Li: " << linha << '\n';
    } else {
        std::cout << "O arquivo estava vazio!\n";
    }
}

5. Ler um CSV: a planilha da Ana

CSV (comma-separated values, "valores separados por vírgula") é o formato de planilha mais simples que existe: cada linha é um registro e os campos são separados por vírgulas. Qualquer planilha (LibreOffice Calc, Excel, Google Planilhas) salva e abre CSV. A Ana tem um na pasta Documentos:

$ cp ~/Documentos/alunos.csv .
$ cat alunos.csv
nome,idade,cidade,nota
Ana,29,Recife,9.5
Bruno,34,Curitiba,7.0
Carla,22,Salvador,8.5
Diego,41,Manaus,6.0
Elisa,27,Porto Alegre,10.0

A primeira linha é o cabeçalho (os nomes das colunas). Para ler, o programa faz duas etapas: pega cada linha do arquivo com getline, e depois separa a linha nos campos, de vírgula em vírgula. Cada aluno vira uma struct:

#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

struct Aluno {
    std::string nome;
    int idade = 0;
    std::string cidade;
    double nota = 0.0;
};

int main() {
    std::ifstream arq("alunos.csv");
    if (!arq) {
        std::cerr << "Erro: não consegui abrir alunos.csv\n";
        return 1;
    }

    std::vector<Aluno> turma;
    std::string linha;
    std::getline(arq, linha);  // pula o cabeçalho

    while (std::getline(arq, linha)) {
        std::stringstream partes(linha);
        std::string idade, nota;
        Aluno a;
        std::getline(partes, a.nome, ',');
        std::getline(partes, idade, ',');
        std::getline(partes, a.cidade, ',');
        std::getline(partes, nota, ',');
        a.idade = std::stoi(idade);
        a.nota = std::stod(nota);
        turma.push_back(a);
    }

    double soma = 0.0;
    for (const auto& a : turma) {
        std::cout << a.nome << " (" << a.cidade << "): " << a.nota << '\n';
        soma += a.nota;
    }
    std::cout << "Média: " << soma / turma.size() << '\n';
}
$ g++ -std=c++23 -Wall -Wextra csv.cpp -o csv
$ ./csv
Ana (Recife): 9.5
Bruno (Curitiba): 7
Carla (Salvador): 8.5
Diego (Manaus): 6
Elisa (Porto Alegre): 10
Média: 8.2
std::getline(arq, linha); // pula o cabeçalho
Lê a primeira linha e não faz nada com ela. Sem isso, o programa tentaria transformar "idade" em número.
std::stringstream partes(linha);
Um stream de texto (do cabeçalho <sstream>): transforma a linha num "arquivo de mentirinha", de onde dá para ler com getline e >> como de um arquivo de verdade.
std::getline(partes, a.nome, ',');
O terceiro argumento muda o separador: em vez de ler até o fim da linha, o getline lê até a próxima vírgula. Cada chamada pega um campo.
std::stoi(idade) / std::stod(nota)
Os campos chegam como texto. std::stoi converte para int e std::stod para double (veja Texto ↔ Número).

Repare que a cidade Porto Alegre, com espaço, chegou inteira: o separador agora é a vírgula, não o espaço. Esse é o motivo de usar getline com ',' em vez de >>.

CSV de verdade pode ser mais complicado

Este programa funciona para CSVs simples, como o da Ana. Planilhas exportadas às vezes trazem campos entre aspas com vírgulas dentro ("Silva, Ana"), ou usam ponto e vírgula como separador (comum em português, porque a vírgula é o separador decimal). Para esses casos, troque o separador (';') ou use uma biblioteca de CSV (veja Usar Bibliotecas Externas). E se uma linha tiver um número estragado, o std::stod lança uma exceção: tratar isso é assunto da próxima aula.


6. std::filesystem: perguntas sobre arquivos C++17

Às vezes você não quer ler o arquivo, só saber coisas sobre ele: existe? Qual o tamanho? Para isso existe a biblioteca <filesystem> ("sistema de arquivos"), do C++17. O nome é comprido, então é costume criar um apelido curto para ele, fs:

#include <filesystem>
#include <iostream>

namespace fs = std::filesystem;

int main() {
    for (const char* nome : {"alunos.csv", "compras.txt", "nota.txt"}) {
        if (fs::exists(nome)) {
            std::cout << nome << ": " << fs::file_size(nome) << " bytes\n";
        } else {
            std::cout << nome << ": não existe\n";
        }
    }
}
$ g++ -std=c++23 -Wall -Wextra fs.cpp -o fs
$ ./fs
alunos.csv: 132 bytes
compras.txt: 30 bytes
nota.txt: não existe
namespace fs = std::filesystem;
Um apelido para o namespace (a "família de nomes", veja Namespaces): fs::exists é o mesmo que std::filesystem::exists. É parecido com o using para tipos.
fs::exists(nome)
Verdadeiro se o arquivo (ou pasta) existe.
fs::file_size(nome)
O tamanho em bytes. A compras.txt tem 28 caracteres (contando as 3 quebras de linha), mas 30 bytes: o ã e o é ocupam 2 bytes cada em UTF-8 (veja Acentos e UTF-8).

Pergunte exists antes de file_size. Pedir o tamanho de um arquivo que não existe derruba o programa:

$ ./fserro
terminate called after throwing an instance of 'std::filesystem::__cxx11::filesystem_error'
  what():  filesystem error: cannot get file size: No such file or directory [nota.txt]
Abortado                   (imagem do núcleo gravada) ./fserro
$ echo $?
134

O file_size lançou uma exceção (No such file or directory = "arquivo ou diretório inexistente"), ninguém a tratou e o programa foi abortado. Você vai aprender a ler e a evitar essa mensagem na aula de Exceções.

A biblioteca faz muito mais (criar pastas com fs::create_directories, listar arquivos de uma pasta, copiar, apagar, renomear), mas isso foge do básico. Por enquanto, guarde exists e file_size.


Quando dá errado

O programa leu "nada", sem mensagem de erro

O que aconteceu: o arquivo não abriu (nome errado, pasta errada) e você não conferiu. Laços de leitura simplesmente não rodam.

Como confirmar: acrescente if (!arq) { std::cerr << "não abriu\n"; return 1; } logo depois de abrir. Confira em que pasta você está rodando o programa (pwd, no terminal): o nome é procurado ali, não na pasta do .cpp.

Como resolver: corrija o nome ou use o caminho completo, como "/home/ana/Documentos/alunos.csv".

"Meu arquivo foi apagado!"

O que aconteceu: você abriu com std::ofstream arq("dados.txt"), que esvazia o arquivo na hora de abrir, mesmo que você não escreva nada.

Como resolver: para acrescentar, use std::ios::app. Para só ler, use std::ifstream.

Escrevi, mas na hora de ler o arquivo estava vazio

O que aconteceu: o ofstream ainda estava aberto, e o texto ainda estava no buffer (seção 4).

Como resolver: feche antes de ler (saida.close();) ou coloque a escrita num bloco { } próprio.

Uma linha vazia a mais, ou o último número contado duas vezes

O que aconteceu: o laço foi escrito com while (!arq.eof()). Com getline, aparece uma linha vazia no fim; com >>, a leitura que falha deixa a variável como estava, e o último número entra duas vezes na conta. O numeros.cpp escrito assim deu 5 notas, média 7.6 em vez de 4 notas, média 7.75.

Como resolver: coloque a leitura na condição: while (std::getline(arq, linha)) ou while (arq >> x).

"terminate called after throwing an instance of 'std::invalid_argument'" ao ler um CSV

O que aconteceu: um campo que deveria ser número não era (o cabeçalho que você esqueceu de pular, um campo vazio, uma vírgula a mais). O std::stoi/std::stod não conseguiu converter e lançou uma exceção.

Como resolver: pule o cabeçalho com um getline antes do laço e trate o erro com try/catch (veja Exceções).


Referência rápida

EscritaSignificado
#include <fstream>Traz std::ofstream e std::ifstream.
std::ofstream arq("x.txt");Abre para escrever, apagando o conteúdo antigo.
std::ofstream arq("x.txt", std::ios::app);Abre para escrever no fim, mantendo o conteúdo.
arq << texto << '\n';Escreve, como o cout.
std::ifstream arq("x.txt");Abre para ler.
if (!arq) { … }Não abriu (ou a última leitura falhou).
while (std::getline(arq, linha))Lê linha a linha até o fim.
while (arq >> x)Lê palavra a palavra ou número a número.
std::getline(partes, campo, ',')Lê até a próxima vírgula (com std::stringstream, de <sstream>).
arq.close();Fecha na hora (normalmente o fim do bloco já fecha).
std::filesystem::exists("x.txt")O arquivo existe? (C++17, <filesystem>)
std::filesystem::file_size("x.txt")Tamanho em bytes (exceção se não existir).

Exercícios

1. Abrir para acrescentar

Complete para abrir vendas.txt sem apagar o que já está nele:

std::ofstream arq("vendas.txt", );

2. Conferir a abertura

Complete a condição que detecta que o arquivo não abriu:

std::ifstream arq("dados.txt");
if () { std::cerr << "Erro\n"; return 1; }

3. Separar por ponto e vírgula

Um CSV usa ; como separador. Complete para ler o primeiro campo da linha:

std::getline(partes, nome, );

4. O arquivo placar.txt tem 10 linhas. O que sobra nele depois de std::ofstream arq("placar.txt"); arq << "fim\n";?

5. O ifstream não encontrou o arquivo e o programa não conferiu. O que acontece no while (std::getline(arq, linha))?

Missão final: quem passou?

Missão: o programa já abre o alunos.csv (na outra aba), pula o cabeçalho e lê o nome de cada aluno. Separe os outros três campos (idade, cidade e nota) com std::getline(partes, …, ','), converta a nota com std::stod e, se ela for 7.0 ou mais, mostre o nome e conte o aluno. No fim deve aparecer Aprovados: 4.

#include <fstream>
#include <iostream>
#include <sstream>
#include <string>

int main() {
    std::ifstream arq("alunos.csv");
    if (!arq) {
        std::cerr << "Erro: não consegui abrir alunos.csv\n";
        return 1;
    }
    std::string linha;
    std::getline(arq, linha);  // pula o cabeçalho

    int aprovados = 0;
    while (std::getline(arq, linha)) {
        std::stringstream partes(linha);
        std::string nome, idade, cidade, nota;
        std::getline(partes, nome, ',');
        // separe os outros três campos e, se a nota for 7 ou mais,
        // mostre o nome e conte mais um aprovado
    }
    std::cout << "Aprovados: " << aprovados << '\n';
}
nome,idade,cidade,nota
Ana,29,Recife,9.5
Bruno,34,Curitiba,7.0
Carla,22,Salvador,8.5
Diego,41,Manaus,6.0
Elisa,27,Porto Alegre,10.0
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>

int main() {
    std::ifstream arq("alunos.csv");
    if (!arq) {
        std::cerr << "Erro: não consegui abrir alunos.csv\n";
        return 1;
    }
    std::string linha;
    std::getline(arq, linha);  // pula o cabeçalho

    int aprovados = 0;
    while (std::getline(arq, linha)) {
        std::stringstream partes(linha);
        std::string nome, idade, cidade, nota;
        std::getline(partes, nome, ',');
        std::getline(partes, idade, ',');
        std::getline(partes, cidade, ',');
        std::getline(partes, nota, ',');
        if (std::stod(nota) >= 7.0) {
            std::cout << nome << '\n';
            ++aprovados;
        }
    }
    std::cout << "Aprovados: " << aprovados << '\n';
}

Resumo

  • Arquivos são streams, como cout e cin: std::ofstream + << para escrever, std::ifstream + getline/>> para ler. Tudo em <fstream>.
  • std::ofstream apaga o conteúdo antigo; com std::ios::app, acrescenta no fim.
  • Leia até o fim com a leitura na condição: while (std::getline(arq, linha)). Nada de eof().
  • Sempre confira se abriu: if (!arq). Sem isso, o erro passa em silêncio.
  • O arquivo fecha sozinho no fim do bloco. Para ler o que acabou de escrever, feche antes (close() ou um bloco).
  • CSV: getline pega a linha; std::stringstream + getline(…, ',') separa os campos; stoi/stod convertem.
  • std::filesystem::exists e file_size respondem perguntas sobre o arquivo sem abri-lo.