Projeto Controle de Estoque

A lojinha de papelaria da Ana cresceu, e o caderno onde ela anotava o estoque não dá mais conta: ela vende cadernos sem perceber que estão acabando e não sabe quanto dinheiro está parado na prateleira. Neste projeto você vai escrever, em sete etapas, o programa que ela precisa: guarda os produtos pelo nome, registra entradas e saídas, avisa quando algo está acabando, mostra um relatório do que vale mais e grava tudo num arquivo, para nada se perder quando o programa fecha.

Você vai praticar

Todos os programas foram compilados e executados na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/cpp/estoque, com o g++ 15.2 e o comando padrão do guia, g++ -std=c++23 -Wall -Wextra estoque.cpp -o estoque. O programa inteiro fica num arquivo só, estoque.cpp, que cresce a cada etapa.


O que vamos construir

O programa pronto, numa sessão da Ana (o que vem depois de estoque> é o que ela digitou):

$ g++ -std=c++23 -Wall -Wextra estoque.cpp -o estoque
$ ./estoque
Lojinha da Ana: 5 produtos carregados.
estoque> listar
borracha     60 un.  R$   1.80
caderno      40 un.  R$  12.50
caneta       90 un.  R$   2.50
cola         12 un.  R$   4.75
mochila       5 un.  R$  89.90
estoque> entrada caneta 30
Entrada registrada.
estoque> saida caderno 32
ALERTA: caderno ficou com 8 (mínimo 10)
Saída registrada.
estoque> saida mochila 10
Quantidade inválida para mochila: pedido 10, há 5
estoque> buscar mochila
mochila: 5 un. a R$ 89.90
estoque> buscar glitter
Não temos glitter.
estoque> alertas
Repor caderno: 8 no estoque, mínimo 10
Repor cola: 12 no estoque, mínimo 15
estoque> relatorio
Produto     Qtd    Preço     Valor
mochila       5    89.90    449.50
caneta      120     2.50    300.00
borracha     60     1.80    108.00
caderno       8    12.50    100.00
cola         12     4.75     57.00
Total                      1014.50
estoque> ajuda
Comandos: listar, entrada NOME QTD, saida NOME QTD, buscar NOME, alertas, relatorio, sair
estoque> sair

Estoque salvo em estoque.csv.
$ cat estoque.csv
borracha,1.8,60,15
caderno,12.5,8,10
caneta,2.5,120,30
cola,4.75,12,15
mochila,89.9,5,2
  1. O produto e o map: cadastrar e listar.
  2. Entrada e saída de mercadoria: find e validação.
  3. Buscar com std::optional.
  4. Alerta de estoque baixo.
  5. Relatório ordenado por valor: std::sort e lambda.
  6. Salvar e carregar o CSV.
  7. O menu.
$ mkdir -p cpp/estoque
$ cd cpp/estoque

Etapa 1 · O produto e o map

Cada produto tem nome, preço, quantidade em estoque e um mínimo: a quantidade abaixo da qual a Ana precisa comprar mais. Quatro informações que andam juntas: uma struct.

E onde guardar os produtos? Num std::vector, achar o "caderno" exigiria olhar um por um. Num std::map (mapa, "dicionário"), cada produto fica guardado debaixo de uma chave, o nome, e o map acha a chave rapidamente. De quebra, ele mantém as chaves em ordem alfabética.

#include <format>
#include <iostream>
#include <map>
#include <string>

struct Produto {
    std::string nome;
    double preco = 0.0;
    int quantidade = 0;
    int minimo = 0;            // abaixo disso, repor
};

using Estoque = std::map<std::string, Produto>;

void cadastrar(Estoque& estoque, const Produto& p) {
    estoque[p.nome] = p;
}

void listar(const Estoque& estoque) {
    for (const auto& [nome, p] : estoque) {
        std::cout << std::format("{:<10}{:>5} un.  R$ {:>6.2f}\n", nome, p.quantidade, p.preco);
    }
}

Estoque estoqueInicial() {
    Estoque estoque;
    cadastrar(estoque, {"caderno", 12.5, 40, 10});
    cadastrar(estoque, {"caneta", 2.5, 120, 30});
    cadastrar(estoque, {"borracha", 1.8, 60, 15});
    cadastrar(estoque, {"mochila", 89.9, 5, 2});
    cadastrar(estoque, {"cola", 4.75, 12, 15});
    return estoque;
}

int main() {
    Estoque estoque = estoqueInicial();
    listar(estoque);
    std::cout << estoque.size() << " produtos\n";
}
TrechoO que faz
struct Produto { … };O molde de um produto. Todos os campos têm valor inicial (= 0): nada fica com lixo.
using Estoque = std::map<std::string, Produto>;Um apelido (using): Estoque é um map cuja chave é o nome (std::string) e o valor é o Produto.
Estoque& estoqueAs funções recebem o estoque por referência: trabalham no original, sem copiar. Quando só leem, const Estoque&.
estoque[p.nome] = p;Guarda o produto debaixo do nome dele. Se o nome já existia, substitui.
for (const auto& [nome, p] : estoque)Percorre o map em ordem de chave. Cada item é um par (chave, valor); [nome, p] dá um nome a cada metade (desestruturação, C++17).
"{:<10}{:>5} un. R$ {:>6.2f}\n"Nome em 10 colunas à esquerda, quantidade em 5 à direita, preço em 6 com 2 casas (std::format).
cadastrar(estoque, {"caderno", 12.5, 40, 10});As chaves montam um Produto na ordem dos campos: nome, preço, quantidade, mínimo.
Estoque estoqueInicial()Monta o estoque de exemplo. Vamos reaproveitá-la em todas as etapas.
$ g++ -std=c++23 -Wall -Wextra estoque.cpp -o estoque
$ ./estoque
borracha     60 un.  R$   1.80
caderno      40 un.  R$  12.50
caneta      120 un.  R$   2.50
cola         12 un.  R$   4.75
mochila       5 un.  R$  89.90
5 produtos

Os produtos foram cadastrados na ordem caderno, caneta, borracha, mochila, cola, mas saíram em ordem alfabética. Quem ordenou foi o std::map.

Missão (etapa 1): a Ana começou a vender tesouras: R$ 15.90, 12 unidades, mínimo 4. Cadastre a tesoura no main, logo depois de criar o estoque. A listagem deve ganhar a linha:

tesoura      12 un.  R$  15.90
6 produtos
#include <format>
#include <iostream>
#include <map>
#include <string>

struct Produto {
    std::string nome;
    double preco = 0.0;
    int quantidade = 0;
    int minimo = 0;            // abaixo disso, repor
};

using Estoque = std::map<std::string, Produto>;

void cadastrar(Estoque& estoque, const Produto& p) {
    estoque[p.nome] = p;
}

void listar(const Estoque& estoque) {
    for (const auto& [nome, p] : estoque) {
        std::cout << std::format("{:<10}{:>5} un.  R$ {:>6.2f}\n", nome, p.quantidade, p.preco);
    }
}

Estoque estoqueInicial() {
    Estoque estoque;
    cadastrar(estoque, {"caderno", 12.5, 40, 10});
    cadastrar(estoque, {"caneta", 2.5, 120, 30});
    cadastrar(estoque, {"borracha", 1.8, 60, 15});
    cadastrar(estoque, {"mochila", 89.9, 5, 2});
    cadastrar(estoque, {"cola", 4.75, 12, 15});
    return estoque;
}

int main() {
    Estoque estoque = estoqueInicial();
    listar(estoque);
    std::cout << estoque.size() << " produtos\n";
}
#include <format>
#include <iostream>
#include <map>
#include <string>

struct Produto {
    std::string nome;
    double preco = 0.0;
    int quantidade = 0;
    int minimo = 0;            // abaixo disso, repor
};

using Estoque = std::map<std::string, Produto>;

void cadastrar(Estoque& estoque, const Produto& p) {
    estoque[p.nome] = p;
}

void listar(const Estoque& estoque) {
    for (const auto& [nome, p] : estoque) {
        std::cout << std::format("{:<10}{:>5} un.  R$ {:>6.2f}\n", nome, p.quantidade, p.preco);
    }
}

Estoque estoqueInicial() {
    Estoque estoque;
    cadastrar(estoque, {"caderno", 12.5, 40, 10});
    cadastrar(estoque, {"caneta", 2.5, 120, 30});
    cadastrar(estoque, {"borracha", 1.8, 60, 15});
    cadastrar(estoque, {"mochila", 89.9, 5, 2});
    cadastrar(estoque, {"cola", 4.75, 12, 15});
    return estoque;
}

int main() {
    Estoque estoque = estoqueInicial();
    cadastrar(estoque, {"tesoura", 15.9, 12, 4});
    listar(estoque);
    std::cout << estoque.size() << " produtos\n";
}

Etapa 2 · Entrada e saída de mercadoria

Chegou mercadoria: entrada, a quantidade sobe. Vendeu: saída, a quantidade desce. As duas precisam de cuidado: o produto tem de existir, a quantidade tem de ser positiva e, na saída, não dá para tirar mais do que há. As duas funções novas (o resto do arquivo continua como na etapa 1):

bool entrada(Estoque& estoque, const std::string& nome, int qtd) {
    auto it = estoque.find(nome);
    if (it == estoque.end()) {
        std::cout << "Produto não encontrado: " << nome << '\n';
        return false;
    }
    if (qtd <= 0) {
        std::cout << "Quantidade inválida: " << qtd << '\n';
        return false;
    }
    it->second.quantidade += qtd;
    return true;
}

bool saida(Estoque& estoque, const std::string& nome, int qtd) {
    auto it = estoque.find(nome);
    if (it == estoque.end()) {
        std::cout << "Produto não encontrado: " << nome << '\n';
        return false;
    }
    Produto& p = it->second;
    if (qtd <= 0 || qtd > p.quantidade) {
        std::cout << std::format("Quantidade inválida para {}: pedido {}, há {}\n",
                                 nome, qtd, p.quantidade);
        return false;
    }
    p.quantidade -= qtd;
    return true;
}
TrechoO que faz
auto it = estoque.find(nome);Procura a chave. Devolve um iterador: uma "seta" apontando para o item achado.
it == estoque.end()Se não achou, a seta aponta para end(), "depois do último". É assim que se pergunta "não existe?".
it->secondO item é um par: it->first é a chave (o nome), it->second é o Produto guardado no map.
Produto& p = it->second;Um apelido (referência) para o produto dentro do map: mexer em p é mexer no estoque. Sem o &, seria uma cópia, e a saída não mudaria nada.
return false;Recusa e avisa quem chamou. A mensagem com o motivo já foi mostrada.

E um main que testa os casos bons e os ruins:

int main() {
    Estoque estoque = estoqueInicial();
    entrada(estoque, "caneta", 30);
    saida(estoque, "caderno", 32);
    saida(estoque, "mochila", 10);      // só há 5
    saida(estoque, "cadreno", 1);       // nome digitado errado
    entrada(estoque, "cola", -5);
    listar(estoque);
}
$ g++ -std=c++23 -Wall -Wextra estoque.cpp -o estoque
$ ./estoque
Quantidade inválida para mochila: pedido 10, há 5
Produto não encontrado: cadreno
Quantidade inválida: -5
borracha     60 un.  R$   1.80
caderno       8 un.  R$  12.50
caneta      150 un.  R$   2.50
cola         12 un.  R$   4.75
mochila       5 un.  R$  89.90

Caneta: 120 + 30 = 150. Caderno: 40 − 32 = 8. As três operações erradas foram recusadas com o motivo, e nenhuma mexeu no estoque.

Quando dá errado: o [] que cria produto

Por que tanto trabalho com find, se estoque["caderno"] já dá o produto? Acrescente no main, antes do listar, uma linha com o nome digitado errado:

    estoque["cadreno"].quantidade -= 1;   // tirar 1 caderno... com o nome errado
$ g++ -std=c++23 -Wall -Wextra estoque.cpp -o estoque
$ ./estoque
Quantidade inválida para mochila: pedido 10, há 5
Produto não encontrado: cadreno
Quantidade inválida: -5
borracha     60 un.  R$   1.80
caderno       8 un.  R$  12.50
cadreno      -1 un.  R$   0.00
caneta      150 un.  R$   2.50
cola         12 un.  R$   4.75
mochila       5 un.  R$  89.90

Nenhum erro, nenhum aviso, e apareceu um produto cadreno com −1 unidade e preço zero. Quando a chave não existe, o [] do map cria um item novo, com um Produto vazio, e devolve esse item. Como evitar: para procurar, use find (ou contains, C++20, que só responde sim ou não). Use [] só quando quer mesmo criar ou substituir, como no cadastrar. Mais em std::map.

Missão (etapa 2): a função entrada está vazia (por isso os avisos de parâmetro não usado). Escreva-a como a saida, mas somando: produto inexistente mostra Produto não encontrado: NOME; quantidade zero ou negativa mostra Quantidade inválida: QTD. Na saída devem aparecer:

Quantidade inválida: -5
Produto não encontrado: tesoura
…
caneta      150 un.  R$   2.50
#include <format>
#include <iostream>
#include <map>
#include <string>

struct Produto {
    std::string nome;
    double preco = 0.0;
    int quantidade = 0;
    int minimo = 0;            // abaixo disso, repor
};

using Estoque = std::map<std::string, Produto>;

void cadastrar(Estoque& estoque, const Produto& p) {
    estoque[p.nome] = p;
}

void listar(const Estoque& estoque) {
    for (const auto& [nome, p] : estoque) {
        std::cout << std::format("{:<10}{:>5} un.  R$ {:>6.2f}\n", nome, p.quantidade, p.preco);
    }
}

bool entrada(Estoque& estoque, const std::string& nome, int qtd) {
    // Missão: procure o produto com find; se não existir, mostre
    // "Produto não encontrado: NOME" e devolva false. Se qtd <= 0, mostre
    // "Quantidade inválida: QTD" e devolva false. Senão, some e devolva true.
    return false;
}

bool saida(Estoque& estoque, const std::string& nome, int qtd) {
    auto it = estoque.find(nome);
    if (it == estoque.end()) {
        std::cout << "Produto não encontrado: " << nome << '\n';
        return false;
    }
    Produto& p = it->second;
    if (qtd <= 0 || qtd > p.quantidade) {
        std::cout << std::format("Quantidade inválida para {}: pedido {}, há {}\n",
                                 nome, qtd, p.quantidade);
        return false;
    }
    p.quantidade -= qtd;
    return true;
}

Estoque estoqueInicial() {
    Estoque estoque;
    cadastrar(estoque, {"caderno", 12.5, 40, 10});
    cadastrar(estoque, {"caneta", 2.5, 120, 30});
    cadastrar(estoque, {"borracha", 1.8, 60, 15});
    cadastrar(estoque, {"mochila", 89.9, 5, 2});
    cadastrar(estoque, {"cola", 4.75, 12, 15});
    return estoque;
}

int main() {
    Estoque estoque = estoqueInicial();
    entrada(estoque, "caneta", 30);
    saida(estoque, "caderno", 32);
    saida(estoque, "mochila", 10);      // só há 5
    saida(estoque, "cadreno", 1);       // nome digitado errado
    entrada(estoque, "cola", -5);
    entrada(estoque, "tesoura", 5);
    listar(estoque);
}
#include <format>
#include <iostream>
#include <map>
#include <string>

struct Produto {
    std::string nome;
    double preco = 0.0;
    int quantidade = 0;
    int minimo = 0;            // abaixo disso, repor
};

using Estoque = std::map<std::string, Produto>;

void cadastrar(Estoque& estoque, const Produto& p) {
    estoque[p.nome] = p;
}

void listar(const Estoque& estoque) {
    for (const auto& [nome, p] : estoque) {
        std::cout << std::format("{:<10}{:>5} un.  R$ {:>6.2f}\n", nome, p.quantidade, p.preco);
    }
}

bool entrada(Estoque& estoque, const std::string& nome, int qtd) {
    auto it = estoque.find(nome);
    if (it == estoque.end()) {
        std::cout << "Produto não encontrado: " << nome << '\n';
        return false;
    }
    if (qtd <= 0) {
        std::cout << "Quantidade inválida: " << qtd << '\n';
        return false;
    }
    it->second.quantidade += qtd;
    return true;
}

bool saida(Estoque& estoque, const std::string& nome, int qtd) {
    auto it = estoque.find(nome);
    if (it == estoque.end()) {
        std::cout << "Produto não encontrado: " << nome << '\n';
        return false;
    }
    Produto& p = it->second;
    if (qtd <= 0 || qtd > p.quantidade) {
        std::cout << std::format("Quantidade inválida para {}: pedido {}, há {}\n",
                                 nome, qtd, p.quantidade);
        return false;
    }
    p.quantidade -= qtd;
    return true;
}

Estoque estoqueInicial() {
    Estoque estoque;
    cadastrar(estoque, {"caderno", 12.5, 40, 10});
    cadastrar(estoque, {"caneta", 2.5, 120, 30});
    cadastrar(estoque, {"borracha", 1.8, 60, 15});
    cadastrar(estoque, {"mochila", 89.9, 5, 2});
    cadastrar(estoque, {"cola", 4.75, 12, 15});
    return estoque;
}

int main() {
    Estoque estoque = estoqueInicial();
    entrada(estoque, "caneta", 30);
    saida(estoque, "caderno", 32);
    saida(estoque, "mochila", 10);      // só há 5
    saida(estoque, "cadreno", 1);       // nome digitado errado
    entrada(estoque, "cola", -5);
    entrada(estoque, "tesoura", 5);
    listar(estoque);
}

Etapa 3 · Buscar com std::optional

Agora a Ana quer consultar um produto: "tem mochila? quanto custa?". Uma função buscar que devolve o Produto… mas e se não existir? Devolver um produto vazio confundiria (um produto com preço zero existe ou não?). Para isso existe o std::optional C++17: uma caixinha que tem um produto ou nada.

std::optional<Produto> buscar(const Estoque& estoque, const std::string& nome) {
    auto it = estoque.find(nome);
    if (it == estoque.end()) {
        return std::nullopt;
    }
    return it->second;
}

Junto com #include <optional> no topo. E o main, com três jeitos de usar o resultado:

int main() {
    Estoque estoque = estoqueInicial();

    std::optional<Produto> p = buscar(estoque, "mochila");
    if (p.has_value()) {
        std::cout << p->nome << ": " << p->quantidade << " un. a R$ " << p->preco << '\n';
    }

    auto q = buscar(estoque, "glitter");
    if (!q) {
        std::cout << "Não temos glitter.\n";
    }

    if (auto r = buscar(estoque, "cola")) {     // cria e testa de uma vez
        std::cout << "cola: " << r->quantidade << " un.\n";
    }
}
TrechoO que faz
std::optional<Produto>"Talvez um Produto".
return std::nullopt;Devolve a caixa vazia (null option, "opção nula").
return it->second;Devolve a caixa com uma cópia do produto. Para ler, basta; para mudar o estoque, use entrada e saida.
p.has_value()Tem alguma coisa dentro?
p->nomeUsa o produto de dentro da caixa (como num ponteiro). Só depois de conferir que ela não está vazia.
if (!q)Um optional vale true quando tem valor. !q: "se está vazio".
if (auto r = buscar(…))Cria r e testa na mesma linha. r só existe dentro do if. É o jeito mais usado.
$ g++ -std=c++23 -Wall -Wextra estoque.cpp -o estoque
$ ./estoque
mochila: 5 un. a R$ 89.9
Não temos glitter.
cola: 12 un.

O preço saiu 89.9 porque aqui usamos cout, não std::format com .2f.

Quando dá errado: abrir a caixa vazia

No fim do main, pegue o preço do glitter sem conferir, com .value():

    std::cout << buscar(estoque, "glitter").value().preco << '\n';
$ g++ -std=c++23 -Wall -Wextra estoque.cpp -o estoque
$ ./estoque
mochila: 5 un. a R$ 89.9
Não temos glitter.
cola: 12 un.
terminate called after throwing an instance of 'std::bad_optional_access'
  what():  bad optional access
Abortado                   (imagem do núcleo gravada) ./estoque

.value() abre a caixa e, se ela estiver vazia, lança a exceção std::bad_optional_access ("acesso ruim a optional"). Ninguém a capturou, e o programa abortou (como no projeto do banco). Pior ainda seria buscar(…)->preco ou *buscar(…) numa caixa vazia: aí não há exceção, é comportamento indefinido, e o programa pode mostrar lixo sem avisar. Como consertar: confira antes (if (auto r = …)) ou dê um valor reserva: buscar(estoque, "glitter").value_or(Produto{}).preco dá 0.

Missão (etapa 3): no main, use buscar duas vezes: mostre borracha: 60 un. e, para "glitter", que não existe, Não temos glitter.

#include <format>
#include <iostream>
#include <map>
#include <optional>
#include <string>

struct Produto {
    std::string nome;
    double preco = 0.0;
    int quantidade = 0;
    int minimo = 0;            // abaixo disso, repor
};

using Estoque = std::map<std::string, Produto>;

void cadastrar(Estoque& estoque, const Produto& p) {
    estoque[p.nome] = p;
}

void listar(const Estoque& estoque) {
    for (const auto& [nome, p] : estoque) {
        std::cout << std::format("{:<10}{:>5} un.  R$ {:>6.2f}\n", nome, p.quantidade, p.preco);
    }
}

std::optional<Produto> buscar(const Estoque& estoque, const std::string& nome) {
    auto it = estoque.find(nome);
    if (it == estoque.end()) {
        return std::nullopt;
    }
    return it->second;
}

Estoque estoqueInicial() {
    Estoque estoque;
    cadastrar(estoque, {"caderno", 12.5, 40, 10});
    cadastrar(estoque, {"caneta", 2.5, 120, 30});
    cadastrar(estoque, {"borracha", 1.8, 60, 15});
    cadastrar(estoque, {"mochila", 89.9, 5, 2});
    cadastrar(estoque, {"cola", 4.75, 12, 15});
    return estoque;
}

int main() {
    Estoque estoque = estoqueInicial();
    // Missão: use buscar para mostrar "borracha: 60 un."
    // e, para "glitter", que não existe, "Não temos glitter."
}
#include <format>
#include <iostream>
#include <map>
#include <optional>
#include <string>

struct Produto {
    std::string nome;
    double preco = 0.0;
    int quantidade = 0;
    int minimo = 0;            // abaixo disso, repor
};

using Estoque = std::map<std::string, Produto>;

void cadastrar(Estoque& estoque, const Produto& p) {
    estoque[p.nome] = p;
}

void listar(const Estoque& estoque) {
    for (const auto& [nome, p] : estoque) {
        std::cout << std::format("{:<10}{:>5} un.  R$ {:>6.2f}\n", nome, p.quantidade, p.preco);
    }
}

std::optional<Produto> buscar(const Estoque& estoque, const std::string& nome) {
    auto it = estoque.find(nome);
    if (it == estoque.end()) {
        return std::nullopt;
    }
    return it->second;
}

Estoque estoqueInicial() {
    Estoque estoque;
    cadastrar(estoque, {"caderno", 12.5, 40, 10});
    cadastrar(estoque, {"caneta", 2.5, 120, 30});
    cadastrar(estoque, {"borracha", 1.8, 60, 15});
    cadastrar(estoque, {"mochila", 89.9, 5, 2});
    cadastrar(estoque, {"cola", 4.75, 12, 15});
    return estoque;
}

int main() {
    Estoque estoque = estoqueInicial();
    if (auto p = buscar(estoque, "borracha")) {
        std::cout << p->nome << ": " << p->quantidade << " un.\n";
    }
    if (!buscar(estoque, "glitter")) {
        std::cout << "Não temos glitter.\n";
    }
}

Etapa 4 · Alerta de estoque baixo

Dois avisos: um na hora, quando uma saída deixa o produto no mínimo ou abaixo dele; e um relatório de alertas, com tudo o que precisa ser reposto. O primeiro são três linhas a mais no fim da saida:

    p.quantidade -= qtd;
    if (p.quantidade <= p.minimo) {
        std::cout << std::format("ALERTA: {} ficou com {} (mínimo {})\n",
                                 nome, p.quantidade, p.minimo);
    }
    return true;
}

O segundo é uma função nova, que percorre o estoque e conta quantos alertas deu, para dizer "nenhum" quando estiver tudo bem:

void alertas(const Estoque& estoque) {
    int contador = 0;
    for (const auto& [nome, p] : estoque) {
        if (p.quantidade <= p.minimo) {
            std::cout << std::format("Repor {}: {} no estoque, mínimo {}\n",
                                     nome, p.quantidade, p.minimo);
            ++contador;
        }
    }
    if (contador == 0) {
        std::cout << "Nenhum produto abaixo do mínimo.\n";
    }
}
int main() {
    Estoque estoque = estoqueInicial();
    saida(estoque, "caderno", 32);
    saida(estoque, "caneta", 50);
    std::cout << "--- alertas ---\n";
    alertas(estoque);
}
$ g++ -std=c++23 -Wall -Wextra estoque.cpp -o estoque
$ ./estoque
ALERTA: caderno ficou com 8 (mínimo 10)
--- alertas ---
Repor caderno: 8 no estoque, mínimo 10
Repor cola: 12 no estoque, mínimo 15

A saída de 32 cadernos disparou o alerta na hora (sobraram 8, mínimo 10). A de 50 canetas não: sobraram 70, bem acima de 30. E o relatório de alertas pegou também a cola, que já começou abaixo do mínimo (12 de 15): nenhuma saída a avisou, só a varredura.

Missão (etapa 4): a função alertas está vazia. Escreva-a: um for pelo estoque e, para cada produto com quantidade menor ou igual ao mínimo, a linha Repor NOME: QTD no estoque, mínimo MIN. Se nenhum precisar, Nenhum produto abaixo do mínimo.

#include <format>
#include <iostream>
#include <map>
#include <string>

struct Produto {
    std::string nome;
    double preco = 0.0;
    int quantidade = 0;
    int minimo = 0;            // abaixo disso, repor
};

using Estoque = std::map<std::string, Produto>;

void cadastrar(Estoque& estoque, const Produto& p) {
    estoque[p.nome] = p;
}

void listar(const Estoque& estoque) {
    for (const auto& [nome, p] : estoque) {
        std::cout << std::format("{:<10}{:>5} un.  R$ {:>6.2f}\n", nome, p.quantidade, p.preco);
    }
}

bool saida(Estoque& estoque, const std::string& nome, int qtd) {
    auto it = estoque.find(nome);
    if (it == estoque.end()) {
        std::cout << "Produto não encontrado: " << nome << '\n';
        return false;
    }
    Produto& p = it->second;
    if (qtd <= 0 || qtd > p.quantidade) {
        std::cout << std::format("Quantidade inválida para {}: pedido {}, há {}\n",
                                 nome, qtd, p.quantidade);
        return false;
    }
    p.quantidade -= qtd;
    if (p.quantidade <= p.minimo) {
        std::cout << std::format("ALERTA: {} ficou com {} (mínimo {})\n",
                                 nome, p.quantidade, p.minimo);
    }
    return true;
}

void alertas(const Estoque& estoque) {
    // Missão: para cada produto com quantidade <= minimo, mostre
    // "Repor NOME: QTD no estoque, mínimo MIN". Se nenhum, mostre
    // "Nenhum produto abaixo do mínimo."
}

Estoque estoqueInicial() {
    Estoque estoque;
    cadastrar(estoque, {"caderno", 12.5, 40, 10});
    cadastrar(estoque, {"caneta", 2.5, 120, 30});
    cadastrar(estoque, {"borracha", 1.8, 60, 15});
    cadastrar(estoque, {"mochila", 89.9, 5, 2});
    cadastrar(estoque, {"cola", 4.75, 12, 15});
    return estoque;
}

int main() {
    Estoque estoque = estoqueInicial();
    saida(estoque, "caderno", 32);
    saida(estoque, "caneta", 50);
    std::cout << "--- alertas ---\n";
    alertas(estoque);
}
#include <format>
#include <iostream>
#include <map>
#include <string>

struct Produto {
    std::string nome;
    double preco = 0.0;
    int quantidade = 0;
    int minimo = 0;            // abaixo disso, repor
};

using Estoque = std::map<std::string, Produto>;

void cadastrar(Estoque& estoque, const Produto& p) {
    estoque[p.nome] = p;
}

void listar(const Estoque& estoque) {
    for (const auto& [nome, p] : estoque) {
        std::cout << std::format("{:<10}{:>5} un.  R$ {:>6.2f}\n", nome, p.quantidade, p.preco);
    }
}

bool saida(Estoque& estoque, const std::string& nome, int qtd) {
    auto it = estoque.find(nome);
    if (it == estoque.end()) {
        std::cout << "Produto não encontrado: " << nome << '\n';
        return false;
    }
    Produto& p = it->second;
    if (qtd <= 0 || qtd > p.quantidade) {
        std::cout << std::format("Quantidade inválida para {}: pedido {}, há {}\n",
                                 nome, qtd, p.quantidade);
        return false;
    }
    p.quantidade -= qtd;
    if (p.quantidade <= p.minimo) {
        std::cout << std::format("ALERTA: {} ficou com {} (mínimo {})\n",
                                 nome, p.quantidade, p.minimo);
    }
    return true;
}

void alertas(const Estoque& estoque) {
    int contador = 0;
    for (const auto& [nome, p] : estoque) {
        if (p.quantidade <= p.minimo) {
            std::cout << std::format("Repor {}: {} no estoque, mínimo {}\n",
                                     nome, p.quantidade, p.minimo);
            ++contador;
        }
    }
    if (contador == 0) {
        std::cout << "Nenhum produto abaixo do mínimo.\n";
    }
}

Estoque estoqueInicial() {
    Estoque estoque;
    cadastrar(estoque, {"caderno", 12.5, 40, 10});
    cadastrar(estoque, {"caneta", 2.5, 120, 30});
    cadastrar(estoque, {"borracha", 1.8, 60, 15});
    cadastrar(estoque, {"mochila", 89.9, 5, 2});
    cadastrar(estoque, {"cola", 4.75, 12, 15});
    return estoque;
}

int main() {
    Estoque estoque = estoqueInicial();
    saida(estoque, "caderno", 32);
    saida(estoque, "caneta", 50);
    std::cout << "--- alertas ---\n";
    alertas(estoque);
}

Etapa 5 · Relatório ordenado por valor

"Onde está o meu dinheiro?" O valor em estoque de um produto é preço × quantidade. A Ana quer uma tabela do que vale mais para o que vale menos, com o total no fim. O map está em ordem de nome, e não dá para reordená-lo: a ordem do map é sempre a das chaves. Então copiamos os produtos para um std::vector e ordenamos o vetor com std::sort, dizendo o critério com uma lambda.

O Produto ganha um método que calcula o valor, e entra a função relatorio (mais #include <algorithm> e #include <vector>):

struct Produto {
    std::string nome;
    double preco = 0.0;
    int quantidade = 0;
    int minimo = 0;            // abaixo disso, repor

    double valor() const { return preco * quantidade; }
};

void relatorio(const Estoque& estoque) {
    std::vector<Produto> lista;
    for (const auto& [nome, p] : estoque) {
        lista.push_back(p);
    }
    std::sort(lista.begin(), lista.end(), [](const Produto& a, const Produto& b) {
        return a.valor() > b.valor();
    });

    std::cout << std::format("{:<10}{:>5}{:>9}{:>10}\n", "Produto", "Qtd", "Preço", "Valor");
    double total = 0.0;
    for (const auto& p : lista) {
        std::cout << std::format("{:<10}{:>5}{:>9.2f}{:>10.2f}\n",
                                 p.nome, p.quantidade, p.preco, p.valor());
        total += p.valor();
    }
    std::cout << std::format("{:<24}{:>10.2f}\n", "Total", total);
}
TrechoO que faz
double valor() constUm método dentro da struct: calcula na hora, então nunca fica desatualizado.
lista.push_back(p);Copia cada produto do map para o vetor.
std::sort(lista.begin(), lista.end(), …)Ordena o vetor inteiro, do começo (begin) ao fim (end).
[](const Produto& a, const Produto& b) { … }A lambda: uma função sem nome, escrita ali mesmo. O sort a chama para comparar dois produtos.
return a.valor() > b.valor();"a vem antes de b se vale mais." Com >, do maior para o menor; com <, do menor para o maior.
"{:<24}{:>10.2f}\n"A linha do total: 24 colunas para "Total" (10 + 5 + 9, as três primeiras colunas juntas) e o valor alinhado embaixo da coluna "Valor".
int main() {
    Estoque estoque = estoqueInicial();
    saida(estoque, "caderno", 32);
    relatorio(estoque);
}
$ g++ -std=c++23 -Wall -Wextra estoque.cpp -o estoque
$ ./estoque
ALERTA: caderno ficou com 8 (mínimo 10)
Produto     Qtd    Preço     Valor
mochila       5    89.90    449.50
caneta      120     2.50    300.00
borracha     60     1.80    108.00
caderno       8    12.50    100.00
cola         12     4.75     57.00
Total                      1014.50

Surpresa para a Ana: as 5 mochilas valem mais que as 120 canetas. Repare também na coluna "Preço", com acento, alinhada certinho: o std::format conta colunas, não bytes (Acentos e UTF-8).

Outra forma: std::ranges::sort

Desde o C++20, dá para escrever std::ranges::sort(lista, comparador), sem begin e end; ou ainda std::ranges::sort(lista, std::greater{}, &Produto::valor), que diz "ordene pelo valor, do maior para o menor". Veja Ranges.

Missão (etapa 5): a Ana quer outra visão: ordene o relatório pela quantidade, da maior para a menor. Só a lambda muda. A primeira linha depois do cabeçalho deve ser a caneta:

Produto     Qtd    Preço     Valor
caneta      120     2.50    300.00
#include <algorithm>
#include <format>
#include <iostream>
#include <map>
#include <string>
#include <vector>

struct Produto {
    std::string nome;
    double preco = 0.0;
    int quantidade = 0;
    int minimo = 0;            // abaixo disso, repor

    double valor() const { return preco * quantidade; }
};

using Estoque = std::map<std::string, Produto>;

void cadastrar(Estoque& estoque, const Produto& p) {
    estoque[p.nome] = p;
}

void listar(const Estoque& estoque) {
    for (const auto& [nome, p] : estoque) {
        std::cout << std::format("{:<10}{:>5} un.  R$ {:>6.2f}\n", nome, p.quantidade, p.preco);
    }
}

bool saida(Estoque& estoque, const std::string& nome, int qtd) {
    auto it = estoque.find(nome);
    if (it == estoque.end()) {
        std::cout << "Produto não encontrado: " << nome << '\n';
        return false;
    }
    Produto& p = it->second;
    if (qtd <= 0 || qtd > p.quantidade) {
        std::cout << std::format("Quantidade inválida para {}: pedido {}, há {}\n",
                                 nome, qtd, p.quantidade);
        return false;
    }
    p.quantidade -= qtd;
    if (p.quantidade <= p.minimo) {
        std::cout << std::format("ALERTA: {} ficou com {} (mínimo {})\n",
                                 nome, p.quantidade, p.minimo);
    }
    return true;
}

void relatorio(const Estoque& estoque) {
    std::vector<Produto> lista;
    for (const auto& [nome, p] : estoque) {
        lista.push_back(p);
    }
    std::sort(lista.begin(), lista.end(), [](const Produto& a, const Produto& b) {
        return a.valor() > b.valor();
    });

    std::cout << std::format("{:<10}{:>5}{:>9}{:>10}\n", "Produto", "Qtd", "Preço", "Valor");
    double total = 0.0;
    for (const auto& p : lista) {
        std::cout << std::format("{:<10}{:>5}{:>9.2f}{:>10.2f}\n",
                                 p.nome, p.quantidade, p.preco, p.valor());
        total += p.valor();
    }
    std::cout << std::format("{:<24}{:>10.2f}\n", "Total", total);
}

Estoque estoqueInicial() {
    Estoque estoque;
    cadastrar(estoque, {"caderno", 12.5, 40, 10});
    cadastrar(estoque, {"caneta", 2.5, 120, 30});
    cadastrar(estoque, {"borracha", 1.8, 60, 15});
    cadastrar(estoque, {"mochila", 89.9, 5, 2});
    cadastrar(estoque, {"cola", 4.75, 12, 15});
    return estoque;
}

int main() {
    Estoque estoque = estoqueInicial();
    saida(estoque, "caderno", 32);
    relatorio(estoque);
}
#include <algorithm>
#include <format>
#include <iostream>
#include <map>
#include <string>
#include <vector>

struct Produto {
    std::string nome;
    double preco = 0.0;
    int quantidade = 0;
    int minimo = 0;            // abaixo disso, repor

    double valor() const { return preco * quantidade; }
};

using Estoque = std::map<std::string, Produto>;

void cadastrar(Estoque& estoque, const Produto& p) {
    estoque[p.nome] = p;
}

void listar(const Estoque& estoque) {
    for (const auto& [nome, p] : estoque) {
        std::cout << std::format("{:<10}{:>5} un.  R$ {:>6.2f}\n", nome, p.quantidade, p.preco);
    }
}

bool saida(Estoque& estoque, const std::string& nome, int qtd) {
    auto it = estoque.find(nome);
    if (it == estoque.end()) {
        std::cout << "Produto não encontrado: " << nome << '\n';
        return false;
    }
    Produto& p = it->second;
    if (qtd <= 0 || qtd > p.quantidade) {
        std::cout << std::format("Quantidade inválida para {}: pedido {}, há {}\n",
                                 nome, qtd, p.quantidade);
        return false;
    }
    p.quantidade -= qtd;
    if (p.quantidade <= p.minimo) {
        std::cout << std::format("ALERTA: {} ficou com {} (mínimo {})\n",
                                 nome, p.quantidade, p.minimo);
    }
    return true;
}

void relatorio(const Estoque& estoque) {
    std::vector<Produto> lista;
    for (const auto& [nome, p] : estoque) {
        lista.push_back(p);
    }
    std::sort(lista.begin(), lista.end(), [](const Produto& a, const Produto& b) {
        return a.quantidade > b.quantidade;
    });

    std::cout << std::format("{:<10}{:>5}{:>9}{:>10}\n", "Produto", "Qtd", "Preço", "Valor");
    double total = 0.0;
    for (const auto& p : lista) {
        std::cout << std::format("{:<10}{:>5}{:>9.2f}{:>10.2f}\n",
                                 p.nome, p.quantidade, p.preco, p.valor());
        total += p.valor();
    }
    std::cout << std::format("{:<24}{:>10.2f}\n", "Total", total);
}

Estoque estoqueInicial() {
    Estoque estoque;
    cadastrar(estoque, {"caderno", 12.5, 40, 10});
    cadastrar(estoque, {"caneta", 2.5, 120, 30});
    cadastrar(estoque, {"borracha", 1.8, 60, 15});
    cadastrar(estoque, {"mochila", 89.9, 5, 2});
    cadastrar(estoque, {"cola", 4.75, 12, 15});
    return estoque;
}

int main() {
    Estoque estoque = estoqueInicial();
    saida(estoque, "caderno", 32);
    relatorio(estoque);
}

Etapa 6 · Salvar e carregar o CSV

Até agora, cada vez que o programa fecha, o estoque volta ao inicial. Vamos gravar o estoque num arquivo CSV (comma-separated values, "valores separados por vírgula"): uma linha por produto, com os campos separados por vírgula. É um formato de texto simples, que qualquer planilha abre.

borracha,1.8,60,15
caderno,12.5,40,10
caneta,2.5,100,30

As duas funções novas (mais #include <fstream> e #include <sstream>):

void salvar(const Estoque& estoque, const std::string& arquivo) {
    std::ofstream arq{arquivo};
    for (const auto& [nome, p] : estoque) {
        arq << nome << ',' << p.preco << ',' << p.quantidade << ',' << p.minimo << '\n';
    }
}

Estoque carregar(const std::string& arquivo) {
    Estoque estoque;
    std::ifstream arq{arquivo};
    std::string linha;
    while (std::getline(arq, linha)) {
        std::istringstream campos{linha};
        std::string nome, preco, qtd, minimo;
        std::getline(campos, nome, ',');
        std::getline(campos, preco, ',');
        std::getline(campos, qtd, ',');
        std::getline(campos, minimo);
        cadastrar(estoque, {nome, std::stod(preco), std::stoi(qtd), std::stoi(minimo)});
    }
    return estoque;
}
TrechoO que faz
std::ofstream arq{arquivo};Abre o arquivo para escrever (output file stream), apagando o conteúdo antigo. Escreve-se com <<, como no cout. O arquivo é fechado sozinho no fim da função (RAII).
std::ifstream arq{arquivo};Abre para ler (input file stream). Se o arquivo não existe, a leitura simplesmente não acha nada.
while (std::getline(arq, linha))Lê linha por linha até o fim (getline).
std::istringstream campos{linha};Trata a linha como um arquivinho, para ler pedaço por pedaço.
std::getline(campos, nome, ',');O terceiro argumento troca o separador: lê até a próxima vírgula. O último campo vai até o fim da linha.
std::stod(preco), std::stoi(qtd)Texto → número: stod = string to double, stoi = string to int (Texto ↔ Número).
int main() {
    Estoque estoque = carregar("estoque.csv");
    if (estoque.empty()) {
        std::cout << "Sem estoque.csv: usando o estoque inicial.\n";
        estoque = estoqueInicial();
    }
    saida(estoque, "caneta", 10);
    listar(estoque);
    salvar(estoque, "estoque.csv");
}

Na primeira execução, o arquivo não existe; na segunda, o programa continua de onde parou:

$ g++ -std=c++23 -Wall -Wextra estoque.cpp -o estoque
$ ./estoque
Sem estoque.csv: usando o estoque inicial.
borracha     60 un.  R$   1.80
caderno      40 un.  R$  12.50
caneta      110 un.  R$   2.50
cola         12 un.  R$   4.75
mochila       5 un.  R$  89.90
$ ./estoque
borracha     60 un.  R$   1.80
caderno      40 un.  R$  12.50
caneta      100 un.  R$   2.50
cola         12 un.  R$   4.75
mochila       5 un.  R$  89.90
$ cat estoque.csv
borracha,1.8,60,15
caderno,12.5,40,10
caneta,2.5,100,30
cola,4.75,12,15
mochila,89.9,5,2

A primeira execução tirou 10 canetas (120 → 110) e salvou; a segunda carregou 110 do arquivo e tirou mais 10 (→ 100). O cat mostra o arquivo de texto que o programa escreveu.

Quando dá errado: uma linha estragada no arquivo

Arquivos de texto podem ser editados à mão, e gente erra. A Ana acrescentou a tesoura escrevendo a quantidade por extenso:

$ echo 'tesoura,15.9,doze,4' >> estoque.csv
$ g++ -std=c++23 -Wall -Wextra estoque.cpp -o estoque
$ ./estoque
terminate called after throwing an instance of 'std::invalid_argument'
  what():  stoi
Abortado                   (imagem do núcleo gravada) ./estoque

std::stoi("doze") não consegue converter e lança std::invalid_argument, cuja mensagem é só o nome da função, stoi. Ninguém capturou, e o programa morreu antes de abrir: uma linha ruim impede a Ana de trabalhar. Como consertar: capturar a exceção por linha, avisar qual linha foi ignorada e seguir com as outras. É a versão final do carregar:

Estoque carregar(const std::string& arquivo) {
    Estoque estoque;
    std::ifstream arq{arquivo};
    std::string linha;
    int numero = 0;
    while (std::getline(arq, linha)) {
        ++numero;
        std::istringstream campos{linha};
        std::string nome, preco, qtd, minimo;
        std::getline(campos, nome, ',');
        std::getline(campos, preco, ',');
        std::getline(campos, qtd, ',');
        std::getline(campos, minimo);
        try {
            cadastrar(estoque, {nome, std::stod(preco), std::stoi(qtd), std::stoi(minimo)});
        } catch (const std::exception&) {
            std::cout << "Linha " << numero << " ignorada: " << linha << '\n';
        }
    }
    return estoque;
}
$ g++ -std=c++23 -Wall -Wextra estoque.cpp -o estoque
$ ./estoque
Linha 6 ignorada: tesoura,15.9,doze,4
borracha     60 un.  R$   1.80
caderno      40 un.  R$  12.50
caneta       90 un.  R$   2.50
cola         12 un.  R$   4.75
mochila       5 un.  R$  89.90

Agora o programa abre, avisa o problema e mostra a linha inteira para a Ana corrigir. Atenção: como o salvar regrava o arquivo só com os produtos carregados, a linha ruim some do estoque.csv ao salvar. Num programa de verdade, valeria guardar uma cópia do arquivo antigo antes.

Missão (etapa 6): esta caixa tem duas abas: o programa e o estoque.csv, com a linha estragada. Rodando como está, o programa aborta. Mude o carregar para contar as linhas e capturar a exceção de cada uma, mostrando Linha 6 ignorada: tesoura,15.9,doze,4 e seguindo em frente. Depois, experimente consertar a linha na aba estoque.csv.

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

struct Produto {
    std::string nome;
    double preco = 0.0;
    int quantidade = 0;
    int minimo = 0;            // abaixo disso, repor

    double valor() const { return preco * quantidade; }
};

using Estoque = std::map<std::string, Produto>;

void cadastrar(Estoque& estoque, const Produto& p) {
    estoque[p.nome] = p;
}

void listar(const Estoque& estoque) {
    for (const auto& [nome, p] : estoque) {
        std::cout << std::format("{:<10}{:>5} un.  R$ {:>6.2f}\n", nome, p.quantidade, p.preco);
    }
}

bool saida(Estoque& estoque, const std::string& nome, int qtd) {
    auto it = estoque.find(nome);
    if (it == estoque.end()) {
        std::cout << "Produto não encontrado: " << nome << '\n';
        return false;
    }
    Produto& p = it->second;
    if (qtd <= 0 || qtd > p.quantidade) {
        std::cout << std::format("Quantidade inválida para {}: pedido {}, há {}\n",
                                 nome, qtd, p.quantidade);
        return false;
    }
    p.quantidade -= qtd;
    if (p.quantidade <= p.minimo) {
        std::cout << std::format("ALERTA: {} ficou com {} (mínimo {})\n",
                                 nome, p.quantidade, p.minimo);
    }
    return true;
}

void salvar(const Estoque& estoque, const std::string& arquivo) {
    std::ofstream arq{arquivo};
    for (const auto& [nome, p] : estoque) {
        arq << nome << ',' << p.preco << ',' << p.quantidade << ',' << p.minimo << '\n';
    }
}

Estoque carregar(const std::string& arquivo) {
    Estoque estoque;
    std::ifstream arq{arquivo};
    std::string linha;
    while (std::getline(arq, linha)) {
        std::istringstream campos{linha};
        std::string nome, preco, qtd, minimo;
        std::getline(campos, nome, ',');
        std::getline(campos, preco, ',');
        std::getline(campos, qtd, ',');
        std::getline(campos, minimo);
        cadastrar(estoque, {nome, std::stod(preco), std::stoi(qtd), std::stoi(minimo)});
    }
    return estoque;
}

Estoque estoqueInicial() {
    Estoque estoque;
    cadastrar(estoque, {"caderno", 12.5, 40, 10});
    cadastrar(estoque, {"caneta", 2.5, 120, 30});
    cadastrar(estoque, {"borracha", 1.8, 60, 15});
    cadastrar(estoque, {"mochila", 89.9, 5, 2});
    cadastrar(estoque, {"cola", 4.75, 12, 15});
    return estoque;
}

int main() {
    Estoque estoque = carregar("estoque.csv");
    if (estoque.empty()) {
        std::cout << "Sem estoque.csv: usando o estoque inicial.\n";
        estoque = estoqueInicial();
    }
    saida(estoque, "caneta", 10);
    listar(estoque);
    salvar(estoque, "estoque.csv");
}
borracha,1.8,60,15
caderno,12.5,40,10
caneta,2.5,100,30
cola,4.75,12,15
mochila,89.9,5,2
tesoura,15.9,doze,4
#include <format>
#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <string>

struct Produto {
    std::string nome;
    double preco = 0.0;
    int quantidade = 0;
    int minimo = 0;            // abaixo disso, repor

    double valor() const { return preco * quantidade; }
};

using Estoque = std::map<std::string, Produto>;

void cadastrar(Estoque& estoque, const Produto& p) {
    estoque[p.nome] = p;
}

void listar(const Estoque& estoque) {
    for (const auto& [nome, p] : estoque) {
        std::cout << std::format("{:<10}{:>5} un.  R$ {:>6.2f}\n", nome, p.quantidade, p.preco);
    }
}

bool saida(Estoque& estoque, const std::string& nome, int qtd) {
    auto it = estoque.find(nome);
    if (it == estoque.end()) {
        std::cout << "Produto não encontrado: " << nome << '\n';
        return false;
    }
    Produto& p = it->second;
    if (qtd <= 0 || qtd > p.quantidade) {
        std::cout << std::format("Quantidade inválida para {}: pedido {}, há {}\n",
                                 nome, qtd, p.quantidade);
        return false;
    }
    p.quantidade -= qtd;
    if (p.quantidade <= p.minimo) {
        std::cout << std::format("ALERTA: {} ficou com {} (mínimo {})\n",
                                 nome, p.quantidade, p.minimo);
    }
    return true;
}

void salvar(const Estoque& estoque, const std::string& arquivo) {
    std::ofstream arq{arquivo};
    for (const auto& [nome, p] : estoque) {
        arq << nome << ',' << p.preco << ',' << p.quantidade << ',' << p.minimo << '\n';
    }
}

Estoque carregar(const std::string& arquivo) {
    Estoque estoque;
    std::ifstream arq{arquivo};
    std::string linha;
    int numero = 0;
    while (std::getline(arq, linha)) {
        ++numero;
        std::istringstream campos{linha};
        std::string nome, preco, qtd, minimo;
        std::getline(campos, nome, ',');
        std::getline(campos, preco, ',');
        std::getline(campos, qtd, ',');
        std::getline(campos, minimo);
        try {
            cadastrar(estoque, {nome, std::stod(preco), std::stoi(qtd), std::stoi(minimo)});
        } catch (const std::exception&) {
            std::cout << "Linha " << numero << " ignorada: " << linha << '\n';
        }
    }
    return estoque;
}

Estoque estoqueInicial() {
    Estoque estoque;
    cadastrar(estoque, {"caderno", 12.5, 40, 10});
    cadastrar(estoque, {"caneta", 2.5, 120, 30});
    cadastrar(estoque, {"borracha", 1.8, 60, 15});
    cadastrar(estoque, {"mochila", 89.9, 5, 2});
    cadastrar(estoque, {"cola", 4.75, 12, 15});
    return estoque;
}

int main() {
    Estoque estoque = carregar("estoque.csv");
    if (estoque.empty()) {
        std::cout << "Sem estoque.csv: usando o estoque inicial.\n";
        estoque = estoqueInicial();
    }
    saida(estoque, "caneta", 10);
    listar(estoque);
    salvar(estoque, "estoque.csv");
}

Etapa 7 · O menu

Falta a Ana poder usar tudo isso sem mexer no código. O main final carrega o arquivo, lê comandos até a Ana digitar sair e salva no fim. As outras funções não mudam.

int main() {
    Estoque estoque = carregar("estoque.csv");
    if (estoque.empty()) {
        estoque = estoqueInicial();
    }
    std::cout << "Lojinha da Ana: " << estoque.size() << " produtos carregados.\n";

    std::string linha;
    while (std::cout << "estoque> " && std::getline(std::cin, linha)) {
        std::istringstream pedaco{linha};
        std::string comando, nome;
        int qtd = 0;
        pedaco >> comando >> nome >> qtd;

        if (comando == "sair") {
            break;
        } else if (comando == "listar") {
            listar(estoque);
        } else if (comando == "entrada") {
            if (entrada(estoque, nome, qtd)) {
                std::cout << "Entrada registrada.\n";
            }
        } else if (comando == "saida") {
            if (saida(estoque, nome, qtd)) {
                std::cout << "Saída registrada.\n";
            }
        } else if (comando == "buscar") {
            if (auto p = buscar(estoque, nome)) {
                std::cout << std::format("{}: {} un. a R$ {:.2f}\n", p->nome, p->quantidade, p->preco);
            } else {
                std::cout << "Não temos " << nome << ".\n";
            }
        } else if (comando == "alertas") {
            alertas(estoque);
        } else if (comando == "relatorio") {
            relatorio(estoque);
        } else {
            std::cout << "Comandos: listar, entrada NOME QTD, saida NOME QTD, buscar NOME,"
                         " alertas, relatorio, sair\n";
        }
    }
    salvar(estoque, "estoque.csv");
    std::cout << "\nEstoque salvo em estoque.csv.\n";
}
TrechoO que faz
while (std::cout << "estoque> " && std::getline(std::cin, linha))Mostra o convite e lê uma linha inteira, até a entrada acabar.
pedaco >> comando >> nome >> qtd;Separa saida caderno 32 em três partes. Se faltarem partes (listar), elas ficam com o valor inicial ("" e 0). Por isso os nomes de produto não podem ter espaços.
comando == "sair"std::string se compara com == diretamente.
break;Sai do laço; o programa segue para o salvar.
else { … Comandos: … }Qualquer coisa desconhecida (até uma linha vazia) mostra a ajuda.
"…, buscar NOME," " alertas, …"Dois textos lado a lado viram um só: o jeito de quebrar uma frase comprida em duas linhas de código.

A sessão completa está no começo da página, em O que vamos construir. Se a Ana apertar Ctrl+D (o "fim da entrada" no terminal) em vez de digitar sair, o getline falha, o laço termina e o estoque é salvo do mesmo jeito.

Missão (etapa 7): crie o comando valor NOME, que mostra o valor em estoque de um produto (Valor em estoque de mochila: R$ 449.50) ou Não temos NOME.. Use buscar e o método valor(). A caixa já digita valor mochila, valor caneta, valor glitter e sair.

#include <algorithm>
#include <format>
#include <fstream>
#include <iostream>
#include <map>
#include <optional>
#include <sstream>
#include <string>
#include <vector>

struct Produto {
    std::string nome;
    double preco = 0.0;
    int quantidade = 0;
    int minimo = 0;            // abaixo disso, repor

    double valor() const { return preco * quantidade; }
};

using Estoque = std::map<std::string, Produto>;

void cadastrar(Estoque& estoque, const Produto& p) {
    estoque[p.nome] = p;
}

void listar(const Estoque& estoque) {
    for (const auto& [nome, p] : estoque) {
        std::cout << std::format("{:<10}{:>5} un.  R$ {:>6.2f}\n", nome, p.quantidade, p.preco);
    }
}

bool entrada(Estoque& estoque, const std::string& nome, int qtd) {
    auto it = estoque.find(nome);
    if (it == estoque.end()) {
        std::cout << "Produto não encontrado: " << nome << '\n';
        return false;
    }
    if (qtd <= 0) {
        std::cout << "Quantidade inválida: " << qtd << '\n';
        return false;
    }
    it->second.quantidade += qtd;
    return true;
}

bool saida(Estoque& estoque, const std::string& nome, int qtd) {
    auto it = estoque.find(nome);
    if (it == estoque.end()) {
        std::cout << "Produto não encontrado: " << nome << '\n';
        return false;
    }
    Produto& p = it->second;
    if (qtd <= 0 || qtd > p.quantidade) {
        std::cout << std::format("Quantidade inválida para {}: pedido {}, há {}\n",
                                 nome, qtd, p.quantidade);
        return false;
    }
    p.quantidade -= qtd;
    if (p.quantidade <= p.minimo) {
        std::cout << std::format("ALERTA: {} ficou com {} (mínimo {})\n",
                                 nome, p.quantidade, p.minimo);
    }
    return true;
}

std::optional<Produto> buscar(const Estoque& estoque, const std::string& nome) {
    auto it = estoque.find(nome);
    if (it == estoque.end()) {
        return std::nullopt;
    }
    return it->second;
}

void alertas(const Estoque& estoque) {
    int contador = 0;
    for (const auto& [nome, p] : estoque) {
        if (p.quantidade <= p.minimo) {
            std::cout << std::format("Repor {}: {} no estoque, mínimo {}\n",
                                     nome, p.quantidade, p.minimo);
            ++contador;
        }
    }
    if (contador == 0) {
        std::cout << "Nenhum produto abaixo do mínimo.\n";
    }
}

void relatorio(const Estoque& estoque) {
    std::vector<Produto> lista;
    for (const auto& [nome, p] : estoque) {
        lista.push_back(p);
    }
    std::sort(lista.begin(), lista.end(), [](const Produto& a, const Produto& b) {
        return a.valor() > b.valor();
    });

    std::cout << std::format("{:<10}{:>5}{:>9}{:>10}\n", "Produto", "Qtd", "Preço", "Valor");
    double total = 0.0;
    for (const auto& p : lista) {
        std::cout << std::format("{:<10}{:>5}{:>9.2f}{:>10.2f}\n",
                                 p.nome, p.quantidade, p.preco, p.valor());
        total += p.valor();
    }
    std::cout << std::format("{:<24}{:>10.2f}\n", "Total", total);
}

void salvar(const Estoque& estoque, const std::string& arquivo) {
    std::ofstream arq{arquivo};
    for (const auto& [nome, p] : estoque) {
        arq << nome << ',' << p.preco << ',' << p.quantidade << ',' << p.minimo << '\n';
    }
}

Estoque carregar(const std::string& arquivo) {
    Estoque estoque;
    std::ifstream arq{arquivo};
    std::string linha;
    int numero = 0;
    while (std::getline(arq, linha)) {
        ++numero;
        std::istringstream campos{linha};
        std::string nome, preco, qtd, minimo;
        std::getline(campos, nome, ',');
        std::getline(campos, preco, ',');
        std::getline(campos, qtd, ',');
        std::getline(campos, minimo);
        try {
            cadastrar(estoque, {nome, std::stod(preco), std::stoi(qtd), std::stoi(minimo)});
        } catch (const std::exception&) {
            std::cout << "Linha " << numero << " ignorada: " << linha << '\n';
        }
    }
    return estoque;
}

Estoque estoqueInicial() {
    Estoque estoque;
    cadastrar(estoque, {"caderno", 12.5, 40, 10});
    cadastrar(estoque, {"caneta", 2.5, 120, 30});
    cadastrar(estoque, {"borracha", 1.8, 60, 15});
    cadastrar(estoque, {"mochila", 89.9, 5, 2});
    cadastrar(estoque, {"cola", 4.75, 12, 15});
    return estoque;
}

int main() {
    Estoque estoque = carregar("estoque.csv");
    if (estoque.empty()) {
        estoque = estoqueInicial();
    }
    std::cout << "Lojinha da Ana: " << estoque.size() << " produtos carregados.\n";

    std::string linha;
    while (std::cout << "estoque> " && std::getline(std::cin, linha)) {
        std::istringstream pedaco{linha};
        std::string comando, nome;
        int qtd = 0;
        pedaco >> comando >> nome >> qtd;

        if (comando == "sair") {
            break;
        } else if (comando == "listar") {
            listar(estoque);
        } else if (comando == "entrada") {
            if (entrada(estoque, nome, qtd)) {
                std::cout << "Entrada registrada.\n";
            }
        } else if (comando == "saida") {
            if (saida(estoque, nome, qtd)) {
                std::cout << "Saída registrada.\n";
            }
        } else if (comando == "buscar") {
            if (auto p = buscar(estoque, nome)) {
                std::cout << std::format("{}: {} un. a R$ {:.2f}\n", p->nome, p->quantidade, p->preco);
            } else {
                std::cout << "Não temos " << nome << ".\n";
            }
        } else if (comando == "alertas") {
            alertas(estoque);
        } else if (comando == "relatorio") {
            relatorio(estoque);
        } else {
            std::cout << "Comandos: listar, entrada NOME QTD, saida NOME QTD, buscar NOME,"
                         " alertas, relatorio, sair\n";
        }
    }
    salvar(estoque, "estoque.csv");
    std::cout << "\nEstoque salvo em estoque.csv.\n";
}
borracha,1.8,60,15
caderno,12.5,40,10
caneta,2.5,100,30
cola,4.75,12,15
mochila,89.9,5,2
#include <algorithm>
#include <format>
#include <fstream>
#include <iostream>
#include <map>
#include <optional>
#include <sstream>
#include <string>
#include <vector>

struct Produto {
    std::string nome;
    double preco = 0.0;
    int quantidade = 0;
    int minimo = 0;            // abaixo disso, repor

    double valor() const { return preco * quantidade; }
};

using Estoque = std::map<std::string, Produto>;

void cadastrar(Estoque& estoque, const Produto& p) {
    estoque[p.nome] = p;
}

void listar(const Estoque& estoque) {
    for (const auto& [nome, p] : estoque) {
        std::cout << std::format("{:<10}{:>5} un.  R$ {:>6.2f}\n", nome, p.quantidade, p.preco);
    }
}

bool entrada(Estoque& estoque, const std::string& nome, int qtd) {
    auto it = estoque.find(nome);
    if (it == estoque.end()) {
        std::cout << "Produto não encontrado: " << nome << '\n';
        return false;
    }
    if (qtd <= 0) {
        std::cout << "Quantidade inválida: " << qtd << '\n';
        return false;
    }
    it->second.quantidade += qtd;
    return true;
}

bool saida(Estoque& estoque, const std::string& nome, int qtd) {
    auto it = estoque.find(nome);
    if (it == estoque.end()) {
        std::cout << "Produto não encontrado: " << nome << '\n';
        return false;
    }
    Produto& p = it->second;
    if (qtd <= 0 || qtd > p.quantidade) {
        std::cout << std::format("Quantidade inválida para {}: pedido {}, há {}\n",
                                 nome, qtd, p.quantidade);
        return false;
    }
    p.quantidade -= qtd;
    if (p.quantidade <= p.minimo) {
        std::cout << std::format("ALERTA: {} ficou com {} (mínimo {})\n",
                                 nome, p.quantidade, p.minimo);
    }
    return true;
}

std::optional<Produto> buscar(const Estoque& estoque, const std::string& nome) {
    auto it = estoque.find(nome);
    if (it == estoque.end()) {
        return std::nullopt;
    }
    return it->second;
}

void alertas(const Estoque& estoque) {
    int contador = 0;
    for (const auto& [nome, p] : estoque) {
        if (p.quantidade <= p.minimo) {
            std::cout << std::format("Repor {}: {} no estoque, mínimo {}\n",
                                     nome, p.quantidade, p.minimo);
            ++contador;
        }
    }
    if (contador == 0) {
        std::cout << "Nenhum produto abaixo do mínimo.\n";
    }
}

void relatorio(const Estoque& estoque) {
    std::vector<Produto> lista;
    for (const auto& [nome, p] : estoque) {
        lista.push_back(p);
    }
    std::sort(lista.begin(), lista.end(), [](const Produto& a, const Produto& b) {
        return a.valor() > b.valor();
    });

    std::cout << std::format("{:<10}{:>5}{:>9}{:>10}\n", "Produto", "Qtd", "Preço", "Valor");
    double total = 0.0;
    for (const auto& p : lista) {
        std::cout << std::format("{:<10}{:>5}{:>9.2f}{:>10.2f}\n",
                                 p.nome, p.quantidade, p.preco, p.valor());
        total += p.valor();
    }
    std::cout << std::format("{:<24}{:>10.2f}\n", "Total", total);
}

void salvar(const Estoque& estoque, const std::string& arquivo) {
    std::ofstream arq{arquivo};
    for (const auto& [nome, p] : estoque) {
        arq << nome << ',' << p.preco << ',' << p.quantidade << ',' << p.minimo << '\n';
    }
}

Estoque carregar(const std::string& arquivo) {
    Estoque estoque;
    std::ifstream arq{arquivo};
    std::string linha;
    int numero = 0;
    while (std::getline(arq, linha)) {
        ++numero;
        std::istringstream campos{linha};
        std::string nome, preco, qtd, minimo;
        std::getline(campos, nome, ',');
        std::getline(campos, preco, ',');
        std::getline(campos, qtd, ',');
        std::getline(campos, minimo);
        try {
            cadastrar(estoque, {nome, std::stod(preco), std::stoi(qtd), std::stoi(minimo)});
        } catch (const std::exception&) {
            std::cout << "Linha " << numero << " ignorada: " << linha << '\n';
        }
    }
    return estoque;
}

Estoque estoqueInicial() {
    Estoque estoque;
    cadastrar(estoque, {"caderno", 12.5, 40, 10});
    cadastrar(estoque, {"caneta", 2.5, 120, 30});
    cadastrar(estoque, {"borracha", 1.8, 60, 15});
    cadastrar(estoque, {"mochila", 89.9, 5, 2});
    cadastrar(estoque, {"cola", 4.75, 12, 15});
    return estoque;
}

int main() {
    Estoque estoque = carregar("estoque.csv");
    if (estoque.empty()) {
        estoque = estoqueInicial();
    }
    std::cout << "Lojinha da Ana: " << estoque.size() << " produtos carregados.\n";

    std::string linha;
    while (std::cout << "estoque> " && std::getline(std::cin, linha)) {
        std::istringstream pedaco{linha};
        std::string comando, nome;
        int qtd = 0;
        pedaco >> comando >> nome >> qtd;

        if (comando == "sair") {
            break;
        } else if (comando == "listar") {
            listar(estoque);
        } else if (comando == "entrada") {
            if (entrada(estoque, nome, qtd)) {
                std::cout << "Entrada registrada.\n";
            }
        } else if (comando == "saida") {
            if (saida(estoque, nome, qtd)) {
                std::cout << "Saída registrada.\n";
            }
        } else if (comando == "buscar") {
            if (auto p = buscar(estoque, nome)) {
                std::cout << std::format("{}: {} un. a R$ {:.2f}\n", p->nome, p->quantidade, p->preco);
            } else {
                std::cout << "Não temos " << nome << ".\n";
            }
        } else if (comando == "valor") {
            if (auto p = buscar(estoque, nome)) {
                std::cout << std::format("Valor em estoque de {}: R$ {:.2f}\n", p->nome, p->valor());
            } else {
                std::cout << "Não temos " << nome << ".\n";
            }
        } else if (comando == "alertas") {
            alertas(estoque);
        } else if (comando == "relatorio") {
            relatorio(estoque);
        } else {
            std::cout << "Comandos: listar, entrada NOME QTD, saida NOME QTD, buscar NOME,"
                         " alertas, relatorio, sair\n";
        }
    }
    salvar(estoque, "estoque.csv");
    std::cout << "\nEstoque salvo em estoque.csv.\n";
}

O código completo

O estoque.cpp final. Compile com g++ -std=c++23 -Wall -Wextra estoque.cpp -o estoque; na primeira vez, sem estoque.csv, ele começa com o estoque de exemplo.

#include <algorithm>
#include <format>
#include <fstream>
#include <iostream>
#include <map>
#include <optional>
#include <sstream>
#include <string>
#include <vector>

struct Produto {
    std::string nome;
    double preco = 0.0;
    int quantidade = 0;
    int minimo = 0;            // abaixo disso, repor

    double valor() const { return preco * quantidade; }
};

using Estoque = std::map<std::string, Produto>;

void cadastrar(Estoque& estoque, const Produto& p) {
    estoque[p.nome] = p;
}

void listar(const Estoque& estoque) {
    for (const auto& [nome, p] : estoque) {
        std::cout << std::format("{:<10}{:>5} un.  R$ {:>6.2f}\n", nome, p.quantidade, p.preco);
    }
}

bool entrada(Estoque& estoque, const std::string& nome, int qtd) {
    auto it = estoque.find(nome);
    if (it == estoque.end()) {
        std::cout << "Produto não encontrado: " << nome << '\n';
        return false;
    }
    if (qtd <= 0) {
        std::cout << "Quantidade inválida: " << qtd << '\n';
        return false;
    }
    it->second.quantidade += qtd;
    return true;
}

bool saida(Estoque& estoque, const std::string& nome, int qtd) {
    auto it = estoque.find(nome);
    if (it == estoque.end()) {
        std::cout << "Produto não encontrado: " << nome << '\n';
        return false;
    }
    Produto& p = it->second;
    if (qtd <= 0 || qtd > p.quantidade) {
        std::cout << std::format("Quantidade inválida para {}: pedido {}, há {}\n",
                                 nome, qtd, p.quantidade);
        return false;
    }
    p.quantidade -= qtd;
    if (p.quantidade <= p.minimo) {
        std::cout << std::format("ALERTA: {} ficou com {} (mínimo {})\n",
                                 nome, p.quantidade, p.minimo);
    }
    return true;
}

std::optional<Produto> buscar(const Estoque& estoque, const std::string& nome) {
    auto it = estoque.find(nome);
    if (it == estoque.end()) {
        return std::nullopt;
    }
    return it->second;
}

void alertas(const Estoque& estoque) {
    int contador = 0;
    for (const auto& [nome, p] : estoque) {
        if (p.quantidade <= p.minimo) {
            std::cout << std::format("Repor {}: {} no estoque, mínimo {}\n",
                                     nome, p.quantidade, p.minimo);
            ++contador;
        }
    }
    if (contador == 0) {
        std::cout << "Nenhum produto abaixo do mínimo.\n";
    }
}

void relatorio(const Estoque& estoque) {
    std::vector<Produto> lista;
    for (const auto& [nome, p] : estoque) {
        lista.push_back(p);
    }
    std::sort(lista.begin(), lista.end(), [](const Produto& a, const Produto& b) {
        return a.valor() > b.valor();
    });

    std::cout << std::format("{:<10}{:>5}{:>9}{:>10}\n", "Produto", "Qtd", "Preço", "Valor");
    double total = 0.0;
    for (const auto& p : lista) {
        std::cout << std::format("{:<10}{:>5}{:>9.2f}{:>10.2f}\n",
                                 p.nome, p.quantidade, p.preco, p.valor());
        total += p.valor();
    }
    std::cout << std::format("{:<24}{:>10.2f}\n", "Total", total);
}

void salvar(const Estoque& estoque, const std::string& arquivo) {
    std::ofstream arq{arquivo};
    for (const auto& [nome, p] : estoque) {
        arq << nome << ',' << p.preco << ',' << p.quantidade << ',' << p.minimo << '\n';
    }
}

Estoque carregar(const std::string& arquivo) {
    Estoque estoque;
    std::ifstream arq{arquivo};
    std::string linha;
    int numero = 0;
    while (std::getline(arq, linha)) {
        ++numero;
        std::istringstream campos{linha};
        std::string nome, preco, qtd, minimo;
        std::getline(campos, nome, ',');
        std::getline(campos, preco, ',');
        std::getline(campos, qtd, ',');
        std::getline(campos, minimo);
        try {
            cadastrar(estoque, {nome, std::stod(preco), std::stoi(qtd), std::stoi(minimo)});
        } catch (const std::exception&) {
            std::cout << "Linha " << numero << " ignorada: " << linha << '\n';
        }
    }
    return estoque;
}

Estoque estoqueInicial() {
    Estoque estoque;
    cadastrar(estoque, {"caderno", 12.5, 40, 10});
    cadastrar(estoque, {"caneta", 2.5, 120, 30});
    cadastrar(estoque, {"borracha", 1.8, 60, 15});
    cadastrar(estoque, {"mochila", 89.9, 5, 2});
    cadastrar(estoque, {"cola", 4.75, 12, 15});
    return estoque;
}

int main() {
    Estoque estoque = carregar("estoque.csv");
    if (estoque.empty()) {
        estoque = estoqueInicial();
    }
    std::cout << "Lojinha da Ana: " << estoque.size() << " produtos carregados.\n";

    std::string linha;
    while (std::cout << "estoque> " && std::getline(std::cin, linha)) {
        std::istringstream pedaco{linha};
        std::string comando, nome;
        int qtd = 0;
        pedaco >> comando >> nome >> qtd;

        if (comando == "sair") {
            break;
        } else if (comando == "listar") {
            listar(estoque);
        } else if (comando == "entrada") {
            if (entrada(estoque, nome, qtd)) {
                std::cout << "Entrada registrada.\n";
            }
        } else if (comando == "saida") {
            if (saida(estoque, nome, qtd)) {
                std::cout << "Saída registrada.\n";
            }
        } else if (comando == "buscar") {
            if (auto p = buscar(estoque, nome)) {
                std::cout << std::format("{}: {} un. a R$ {:.2f}\n", p->nome, p->quantidade, p->preco);
            } else {
                std::cout << "Não temos " << nome << ".\n";
            }
        } else if (comando == "alertas") {
            alertas(estoque);
        } else if (comando == "relatorio") {
            relatorio(estoque);
        } else {
            std::cout << "Comandos: listar, entrada NOME QTD, saida NOME QTD, buscar NOME,"
                         " alertas, relatorio, sair\n";
        }
    }
    salvar(estoque, "estoque.csv");
    std::cout << "\nEstoque salvo em estoque.csv.\n";
}

Desafios extras

  1. Cadastrar pelo menu. Comando novo NOME PRECO QTD MIN. Recuse se o produto já existir (contains, C++20).
  2. Remover. Comando remover NOME com estoque.erase(nome). O erase devolve quantos itens apagou (0 ou 1): use para avisar "não existe".
  3. Nomes com espaço. "caneta azul" quebra o menu. Troque o separador do menu, por exemplo saida;caneta azul;3, com getline(pedaco, nome, ';').
  4. Histórico. Grave cada entrada e saída num segundo arquivo, movimentos.csv, aberto com std::ofstream arq{"movimentos.csv", std::ios::app} (app = acrescentar no fim, sem apagar).
  5. Cópia de segurança. Antes de salvar, renomeie o arquivo antigo para estoque.csv.bak com std::filesystem::rename.
  6. Busca por pedaço. buscar cad lista todos os produtos cujo nome contém "cad" (nome.find(trecho) != std::string::npos, veja a referência de std::string).

Resumo

  • std::map<std::string, Produto> guarda pela chave e percorre em ordem alfabética; for (const auto& [nome, p] : estoque) separa chave e valor.
  • Para procurar, find e compare com end(). O [] cria a chave que não existe: um erro de digitação vira produto novo.
  • Produto& p = it->second; mexe no produto dentro do map; sem &, numa cópia.
  • std::optional<Produto> = "um produto ou nada". Devolva std::nullopt quando não achar; teste com if (auto p = buscar(…)). .value() numa caixa vazia lança std::bad_optional_access.
  • Para ordenar por outro critério, copie para um std::vector e use std::sort(início, fim, lambda); a lambda responde "a vem antes de b?".
  • CSV: std::ofstream + << para gravar; std::ifstream + getline(campos, x, ',') + stoi/stod para ler. Capture a exceção de conversão por linha, senão uma linha ruim derruba o programa.