Projeto Agenda de Contatos

A Ana quer guardar os contatos dos clientes da lojinha: nome, telefone e e-mail. Neste projeto você vai construir uma agenda de terminal com um menu para adicionar, listar, buscar e remover contatos, e que guarda tudo num arquivo: ao abrir o programa no dia seguinte, os contatos continuam lá. É o primeiro projeto do guia com dados que sobrevivem ao fim do programa.

Você vai praticar
  • Criar um tipo próprio com struct.
  • Guardar vários contatos num std::vector: push_back, size, erase.
  • Montar um menu com while e switch.
  • Ler linhas inteiras com getline, e o problema de misturá-lo com cin >>.
  • Comparar textos sem diferenciar maiúsculas e minúsculas (funções de string, tolower).
  • Gravar e ler um arquivo de texto com fstream.

O que vamos construir

A agenda pronta, aberta pela segunda vez na máquina da Ana (os contatos vieram do arquivo gravado na primeira vez). Depois de "Opção:" e das outras perguntas está o que ela digitou:

$ ./agenda
=== Agenda da Ana ===
Carregados 2 contatos de agenda.txt.

1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair
Opção: 1
Nome: Carla Dias
Telefone: (31) 90000-0003
E-mail: carla@exemplo.com
Contato adicionado: Carla Dias

1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair
Opção: 3
Buscar por: LI
2. Bruno Lima | (21) 90000-0002 | bruno@exemplo.com

1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair
Opção: 0
Agenda salva em agenda.txt (3 contatos).
Até logo!
$ cat agenda.txt
Ana Souza;(11) 90000-0001;ana@exemplo.com
Bruno Lima;(21) 90000-0002;bruno@exemplo.com
Carla Dias;(31) 90000-0003;carla@exemplo.com

Os telefones e e-mails são de mentira (exemplo.com é um domínio reservado para exemplos). Seis etapas até lá:

  1. O contato e a lista (struct, vector).
  2. O menu e o "adicionar" (while, switch, getline e std::ws).
  3. Buscar sem diferenciar maiúsculas (tolower, contains).
  4. Remover (erase e validação do número).
  5. Salvar no arquivo (std::ofstream).
  6. Carregar do arquivo (std::ifstream, istringstream).

Antes de começar

Este projeto usa o Nível 1 inteiro e um pouco do Nível 2. Você precisa ter visto Structs, std::vector, Funções, Por Valor × Por Referência e getline; a aula Ler e Escrever Arquivos ajuda nas etapas 5 e 6. Fazer antes a Calculadora também ajuda: a validação da entrada é parecida.

$ mkdir -p cpp/agenda
$ cd cpp/agenda

Etapa 1 · O contato e a lista

Um contato tem três informações: nome, telefone e e-mail. Poderíamos usar três variáveis soltas, mas com 50 contatos seriam 150 variáveis. Em vez disso, criamos um tipo novo, Contato, que junta as três (veja Structs). E para guardar vários contatos, um std::vector<Contato>: uma lista que cresce.

#include <iostream>
#include <string>
#include <vector>

struct Contato {
    std::string nome;
    std::string telefone;
    std::string email;
};

void mostrar(std::size_t numero, const Contato& c) {
    std::cout << numero << ". " << c.nome << " | " << c.telefone << " | " << c.email << '\n';
}

void listar(const std::vector<Contato>& agenda) {
    if (agenda.empty()) {
        std::cout << "A agenda está vazia.\n";
        return;
    }
    std::cout << "--- Contatos (" << agenda.size() << ") ---\n";
    for (std::size_t i = 0; i < agenda.size(); ++i) {
        mostrar(i + 1, agenda[i]);
    }
}

int main() {
    std::cout << "=== Agenda da Ana ===\n";
    std::vector<Contato> agenda{
        {"Ana Souza", "(11) 90000-0001", "ana@exemplo.com"},
        {"Bruno Lima", "(21) 90000-0002", "bruno@exemplo.com"},
    };
    listar(agenda);
}
struct Contato { … };
Define o tipo Contato, com três campos std::string. Repare no ; depois da chave: ele é obrigatório no fim de uma struct.
void mostrar(std::size_t numero, const Contato& c)
Mostra um contato numa linha, com o número dele na frente. O contato é recebido por const&: sem cópia, sem poder mudar (veja Boas Práticas). std::size_t é o tipo dos tamanhos e posições de um vector (um inteiro sem sinal).
c.nome
O ponto acessa um campo do contato: "o nome de c".
agenda.empty()
true se a lista estiver vazia. Nesse caso, avisamos e saímos com return;.
for (std::size_t i = 0; i < agenda.size(); ++i)
Percorre a lista pela posição. Aqui o for com índice é melhor que o range-for, porque precisamos do número de cada contato. O i é std::size_t, do mesmo tipo de size(): assim não há o aviso de "sinais diferentes".
mostrar(i + 1, agenda[i])
As posições do vector começam em 0, mas pessoas contam a partir de 1. Por isso mostramos i + 1.
std::vector<Contato> agenda{ {…}, {…}, };
Cria a lista já com dois contatos. Cada {"Ana Souza", "(11) 90000-0001", "ana@exemplo.com"} preenche os campos na ordem em que foram declarados na struct.
$ g++ -std=c++23 -Wall -Wextra agenda.cpp -o agenda
$ ./agenda
=== Agenda da Ana ===
--- Contatos (2) ---
1. Ana Souza | (11) 90000-0001 | ana@exemplo.com
2. Bruno Lima | (21) 90000-0002 | bruno@exemplo.com

Missão (etapa 1): acrescente um terceiro contato à lista inicial: Carla Dias, telefone (31) 90000-0003, e-mail carla@exemplo.com. A listagem deve mostrar --- Contatos (3) ---.

#include <iostream>
#include <string>
#include <vector>

struct Contato {
    std::string nome;
    std::string telefone;
    std::string email;
};

void mostrar(std::size_t numero, const Contato& c) {
    std::cout << numero << ". " << c.nome << " | " << c.telefone << " | " << c.email << '\n';
}

void listar(const std::vector<Contato>& agenda) {
    if (agenda.empty()) {
        std::cout << "A agenda está vazia.\n";
        return;
    }
    std::cout << "--- Contatos (" << agenda.size() << ") ---\n";
    for (std::size_t i = 0; i < agenda.size(); ++i) {
        mostrar(i + 1, agenda[i]);
    }
}

int main() {
    std::cout << "=== Agenda da Ana ===\n";
    std::vector<Contato> agenda{
        {"Ana Souza", "(11) 90000-0001", "ana@exemplo.com"},
        {"Bruno Lima", "(21) 90000-0002", "bruno@exemplo.com"},
    };
    listar(agenda);
}
#include <iostream>
#include <string>
#include <vector>

struct Contato {
    std::string nome;
    std::string telefone;
    std::string email;
};

void mostrar(std::size_t numero, const Contato& c) {
    std::cout << numero << ". " << c.nome << " | " << c.telefone << " | " << c.email << '\n';
}

void listar(const std::vector<Contato>& agenda) {
    if (agenda.empty()) {
        std::cout << "A agenda está vazia.\n";
        return;
    }
    std::cout << "--- Contatos (" << agenda.size() << ") ---\n";
    for (std::size_t i = 0; i < agenda.size(); ++i) {
        mostrar(i + 1, agenda[i]);
    }
}

int main() {
    std::cout << "=== Agenda da Ana ===\n";
    std::vector<Contato> agenda{
        {"Ana Souza", "(11) 90000-0001", "ana@exemplo.com"},
        {"Bruno Lima", "(21) 90000-0002", "bruno@exemplo.com"},
        {"Carla Dias", "(31) 90000-0003", "carla@exemplo.com"},
    };
    listar(agenda);
}

Etapa 2 · O menu e o "adicionar"

Agora a agenda vira um programa interativo: um menu que se repete até a pessoa escolher 0. Cada opção chama uma função. Nesta etapa, o menu tem "Adicionar", "Listar" e "Sair". Estas são as partes novas (as funções mostrar e listar e a struct não mudam):

int ler_opcao() {
    int opcao = -1;
    std::cout << "Opção: ";
    if (!(std::cin >> opcao)) {
        if (std::cin.eof()) {
            return 0;  // a entrada acabou: sai (e salva)
        }
        std::cin.clear();
        std::string lixo;
        std::getline(std::cin, lixo);
        return -1;  // não era número: opção inválida
    }
    return opcao;
}

void adicionar(std::vector<Contato>& agenda) {
    Contato novo;
    std::cout << "Nome: ";
    std::getline(std::cin >> std::ws, novo.nome);
    std::cout << "Telefone: ";
    std::getline(std::cin >> std::ws, novo.telefone);
    std::cout << "E-mail: ";
    std::getline(std::cin >> std::ws, novo.email);
    agenda.push_back(novo);
    std::cout << "Contato adicionado: " << novo.nome << '\n';
}

int main() {
    std::cout << "=== Agenda da Ana ===\n";
    std::vector<Contato> agenda{
        {"Ana Souza", "(11) 90000-0001", "ana@exemplo.com"},
        {"Bruno Lima", "(21) 90000-0002", "bruno@exemplo.com"},
    };

    int opcao = -1;
    while (opcao != 0) {
        std::cout << "\n1. Adicionar  2. Listar  0. Sair\n";
        opcao = ler_opcao();
        switch (opcao) {
            case 1: adicionar(agenda); break;
            case 2: listar(agenda); break;
            case 0: break;
            default: std::cout << "Opção inválida.\n";
        }
    }
    std::cout << "Até logo!\n";
}
ParteO que faz
ler_opcao()Lê o número da opção. Se a pessoa digitar texto, limpa o erro do cin e devolve -1 ("opção inválida"). Se a entrada acabar (Ctrl+D), devolve 0, como se ela tivesse escolhido sair. É a mesma técnica da etapa 5 da Calculadora.
void adicionar(std::vector<Contato>& agenda)Recebe a agenda por referência (&, sem const): a função precisa mudar a lista original, acrescentando um contato. Sem o &, ela acrescentaria numa cópia, que sumiria no fim da função.
Contato novo;Um contato vazio, preenchido campo por campo.
std::getline(std::cin >> std::ws, novo.nome);Lê a linha inteira, com espaços. O >> sozinho pararia no primeiro espaço e leria só "Carla". O std::ws é explicado logo abaixo.
agenda.push_back(novo);Acrescenta o contato no fim da lista.
int opcao = -1; while (opcao != 0)O laço do menu: repete até a opção ser 0. Começa com -1 para entrar no laço na primeira vez.
case 0: break;Nada a fazer: o while vê o 0 e termina. Sem esse case, o 0 cairia no default e mostraria "Opção inválida".
$ g++ -std=c++23 -Wall -Wextra agenda.cpp -o agenda
$ ./agenda
=== Agenda da Ana ===

1. Adicionar  2. Listar  0. Sair
Opção: 2
--- Contatos (2) ---
1. Ana Souza | (11) 90000-0001 | ana@exemplo.com
2. Bruno Lima | (21) 90000-0002 | bruno@exemplo.com

1. Adicionar  2. Listar  0. Sair
Opção: 1
Nome: Carla Dias
Telefone: (31) 90000-0003
E-mail: carla@exemplo.com
Contato adicionado: Carla Dias

1. Adicionar  2. Listar  0. Sair
Opção: 2
--- Contatos (3) ---
1. Ana Souza | (11) 90000-0001 | ana@exemplo.com
2. Bruno Lima | (21) 90000-0002 | bruno@exemplo.com
3. Carla Dias | (31) 90000-0003 | carla@exemplo.com

1. Adicionar  2. Listar  0. Sair
Opção: 7
Opção inválida.

1. Adicionar  2. Listar  0. Sair
Opção: 0
Até logo!

Por que o std::ws: o getline que "pula" a pergunta

Na primeira versão, a Ana escreveu só std::getline(std::cin, novo.nome);. Veja o que aconteceu:

$ ./agenda
=== Agenda da Ana ===

1. Adicionar  2. Listar  0. Sair
Opção: 1
Nome: Telefone: Carla Dias           « o "Nome:" não esperou!
E-mail: (31) 90000-0003
Contato adicionado:

1. Adicionar  2. Listar  0. Sair
Opção: 2
--- Contatos (3) ---
1. Ana Souza | (11) 90000-0001 | ana@exemplo.com
2. Bruno Lima | (21) 90000-0002 | bruno@exemplo.com
3.  | Carla Dias | (31) 90000-0003  « nome vazio, e tudo deslocado uma casa

Quando a Ana digitou 1 e apertou Enter, a entrada recebeu dois caracteres: o 1 e uma quebra de linha (\n). O std::cin >> opcao leu o 1 e deixou o \n lá. Aí o getline do nome, que lê "até o fim da linha", encontrou o \n imediatamente e leu uma linha vazia, sem esperar. O nome digitado foi parar no telefone, o telefone no e-mail.

O std::ws (de whitespace, "espaço em branco") resolve: std::cin >> std::ws descarta espaços e quebras de linha que estejam esperando, e só então o getline lê. Esse problema de misturar >> e getline é tão comum que tem uma seção só para ele na aula getline.

Missão (etapa 2): este código tem o problema do getline que "pula" a pergunta. Rode e veja o contato sair torto na listagem. Depois conserte as três leituras do adicionar com std::cin >> std::ws. A entrada adiciona a Carla Dias e lista.

#include <iostream>
#include <string>
#include <vector>

struct Contato {
    std::string nome;
    std::string telefone;
    std::string email;
};

void mostrar(std::size_t numero, const Contato& c) {
    std::cout << numero << ". " << c.nome << " | " << c.telefone << " | " << c.email << '\n';
}

void listar(const std::vector<Contato>& agenda) {
    if (agenda.empty()) {
        std::cout << "A agenda está vazia.\n";
        return;
    }
    std::cout << "--- Contatos (" << agenda.size() << ") ---\n";
    for (std::size_t i = 0; i < agenda.size(); ++i) {
        mostrar(i + 1, agenda[i]);
    }
}

int ler_opcao() {
    int opcao = -1;
    std::cout << "Opção: ";
    if (!(std::cin >> opcao)) {
        if (std::cin.eof()) {
            return 0;  // a entrada acabou: sai (e salva)
        }
        std::cin.clear();
        std::string lixo;
        std::getline(std::cin, lixo);
        return -1;  // não era número: opção inválida
    }
    return opcao;
}

void adicionar(std::vector<Contato>& agenda) {
    Contato novo;
    std::cout << "Nome: ";
    std::getline(std::cin, novo.nome);
    std::cout << "Telefone: ";
    std::getline(std::cin, novo.telefone);
    std::cout << "E-mail: ";
    std::getline(std::cin, novo.email);
    agenda.push_back(novo);
    std::cout << "Contato adicionado: " << novo.nome << '\n';
}

int main() {
    std::cout << "=== Agenda da Ana ===\n";
    std::vector<Contato> agenda{
        {"Ana Souza", "(11) 90000-0001", "ana@exemplo.com"},
        {"Bruno Lima", "(21) 90000-0002", "bruno@exemplo.com"},
    };

    int opcao = -1;
    while (opcao != 0) {
        std::cout << "\n1. Adicionar  2. Listar  0. Sair\n";
        opcao = ler_opcao();
        switch (opcao) {
            case 1: adicionar(agenda); break;
            case 2: listar(agenda); break;
            case 0: break;
            default: std::cout << "Opção inválida.\n";
        }
    }
    std::cout << "Até logo!\n";
}
#include <iostream>
#include <string>
#include <vector>

struct Contato {
    std::string nome;
    std::string telefone;
    std::string email;
};

void mostrar(std::size_t numero, const Contato& c) {
    std::cout << numero << ". " << c.nome << " | " << c.telefone << " | " << c.email << '\n';
}

void listar(const std::vector<Contato>& agenda) {
    if (agenda.empty()) {
        std::cout << "A agenda está vazia.\n";
        return;
    }
    std::cout << "--- Contatos (" << agenda.size() << ") ---\n";
    for (std::size_t i = 0; i < agenda.size(); ++i) {
        mostrar(i + 1, agenda[i]);
    }
}

int ler_opcao() {
    int opcao = -1;
    std::cout << "Opção: ";
    if (!(std::cin >> opcao)) {
        if (std::cin.eof()) {
            return 0;  // a entrada acabou: sai (e salva)
        }
        std::cin.clear();
        std::string lixo;
        std::getline(std::cin, lixo);
        return -1;  // não era número: opção inválida
    }
    return opcao;
}

void adicionar(std::vector<Contato>& agenda) {
    Contato novo;
    std::cout << "Nome: ";
    std::getline(std::cin >> std::ws, novo.nome);
    std::cout << "Telefone: ";
    std::getline(std::cin >> std::ws, novo.telefone);
    std::cout << "E-mail: ";
    std::getline(std::cin >> std::ws, novo.email);
    agenda.push_back(novo);
    std::cout << "Contato adicionado: " << novo.nome << '\n';
}

int main() {
    std::cout << "=== Agenda da Ana ===\n";
    std::vector<Contato> agenda{
        {"Ana Souza", "(11) 90000-0001", "ana@exemplo.com"},
        {"Bruno Lima", "(21) 90000-0002", "bruno@exemplo.com"},
    };

    int opcao = -1;
    while (opcao != 0) {
        std::cout << "\n1. Adicionar  2. Listar  0. Sair\n";
        opcao = ler_opcao();
        switch (opcao) {
            case 1: adicionar(agenda); break;
            case 2: listar(agenda); break;
            case 0: break;
            default: std::cout << "Opção inválida.\n";
        }
    }
    std::cout << "Até logo!\n";
}

Etapa 3 · Buscar sem diferenciar maiúsculas

Com muitos contatos, a Ana quer digitar um pedaço do nome e ver só quem combina. A primeira ideia: percorrer a lista e perguntar se o nome contém o texto buscado. O C++23 tem um método para isso, contains (veja Funções de String; em versões antigas, usa-se nome.find(busca) != std::string::npos):

        if (agenda[i].nome.contains(busca)) {
$ ./agenda
=== Agenda da Ana ===

1. Adicionar  2. Listar  3. Buscar  0. Sair
Opção: 3
Buscar por: ana
Nenhum contato encontrado com "ana".

1. Adicionar  2. Listar  3. Buscar  0. Sair
Opção: 3
Buscar por: Ana
1. Ana Souza | (11) 90000-0001 | ana@exemplo.com

1. Adicionar  2. Listar  3. Buscar  0. Sair
Opção: 0
Até logo!

Funciona, mas só com a letra maiúscula certa: para o computador, a e A são caracteres diferentes. A solução é comparar as duas coisas em minúsculas: o nome e o texto buscado. Para isso, uma função que troca cada letra pela minúscula:

// Devolve uma cópia do texto com as letras A-Z trocadas por a-z.
std::string minusculas(std::string texto) {
    for (char& letra : texto) {
        letra = static_cast<char>(std::tolower(static_cast<unsigned char>(letra)));
    }
    return texto;
}

void buscar(const std::vector<Contato>& agenda) {
    std::string busca;
    std::cout << "Buscar por: ";
    std::getline(std::cin >> std::ws, busca);
    const std::string procurado = minusculas(busca);

    int encontrados = 0;
    for (std::size_t i = 0; i < agenda.size(); ++i) {
        if (minusculas(agenda[i].nome).contains(procurado)) {
            mostrar(i + 1, agenda[i]);
            ++encontrados;
        }
    }
    if (encontrados == 0) {
        std::cout << "Nenhum contato encontrado com \"" << busca << "\".\n";
    }
}
std::string minusculas(std::string texto)
Aqui o parâmetro é por valor, de propósito: a função recebe uma cópia, muda a cópia e a devolve. O nome original do contato fica intacto.
for (char& letra : texto)
Percorre cada caractere por referência (&), para poder trocá-lo dentro do texto.
std::tolower(…)
De to lower, "para minúscula": devolve a minúscula de uma letra ('A' → 'a') e devolve qualquer outro caractere sem mudar. Vem do <cctype>.
static_cast<unsigned char>(letra)
Um detalhe chato, mas importante: o tolower só aceita valores de 0 a 255, e um char com acento pode ser negativo. Converter para unsigned char antes evita comportamento indefinido (veja Conversão de Tipos). O static_cast<char> de fora converte o resultado de volta para char.
const std::string procurado = minusculas(busca);
Converte o texto buscado uma vez só, antes do laço.
minusculas(agenda[i].nome).contains(procurado)
"O nome, em minúsculas, contém o texto procurado, em minúsculas?"
int encontrados
Conta quantos combinaram, para avisar quando não houver nenhum.
$ g++ -std=c++23 -Wall -Wextra agenda.cpp -o agenda
$ ./agenda
=== Agenda da Ana ===

1. Adicionar  2. Listar  3. Buscar  0. Sair
Opção: 3
Buscar por: ana
1. Ana Souza | (11) 90000-0001 | ana@exemplo.com

1. Adicionar  2. Listar  3. Buscar  0. Sair
Opção: 3
Buscar por: LI
2. Bruno Lima | (21) 90000-0002 | bruno@exemplo.com

1. Adicionar  2. Listar  3. Buscar  0. Sair
Opção: 3
Buscar por: zé
Nenhum contato encontrado com "zé".

1. Adicionar  2. Listar  3. Buscar  0. Sair
Opção: 1
Nome: Érica Melo
Telefone: (41) 90000-0004
E-mail: erica@exemplo.com
Contato adicionado: Érica Melo

1. Adicionar  2. Listar  3. Buscar  0. Sair
Opção: 3
Buscar por: érica
Nenhum contato encontrado com "érica".      « o acento atrapalhou

1. Adicionar  2. Listar  3. Buscar  0. Sair
Opção: 3
Buscar por: rica
3. Érica Melo | (41) 90000-0004 | erica@exemplo.com

1. Adicionar  2. Listar  3. Buscar  0. Sair
Opção: 0
Até logo!
Só as letras sem acento (ASCII)

A busca encontrou "Ana" com ana e "Lima" com LI, mas não encontrou "Érica" com érica. O motivo: no UTF-8, que é como o Linux guarda o texto, uma letra acentuada como É ocupa dois char, e o tolower olha um char de cada vez. Ele só sabe converter as 26 letras sem acento (a tabela ASCII). Resolver isso direito exige uma biblioteca especializada; a aula Acentos e UTF-8 explica o porquê. Para a agenda da Ana, buscar pela parte sem acento (rica) é suficiente.

Missão (etapa 3): a função minusculas já existe, mas a busca ainda compara o texto exato. Mude a condição do if no buscar para comparar minusculas(agenda[i].nome) com procurado. A entrada busca ana, em minúsculas.

#include <cctype>
#include <iostream>
#include <string>
#include <vector>

struct Contato {
    std::string nome;
    std::string telefone;
    std::string email;
};

void mostrar(std::size_t numero, const Contato& c) {
    std::cout << numero << ". " << c.nome << " | " << c.telefone << " | " << c.email << '\n';
}

void listar(const std::vector<Contato>& agenda) {
    if (agenda.empty()) {
        std::cout << "A agenda está vazia.\n";
        return;
    }
    std::cout << "--- Contatos (" << agenda.size() << ") ---\n";
    for (std::size_t i = 0; i < agenda.size(); ++i) {
        mostrar(i + 1, agenda[i]);
    }
}

int ler_opcao() {
    int opcao = -1;
    std::cout << "Opção: ";
    if (!(std::cin >> opcao)) {
        if (std::cin.eof()) {
            return 0;  // a entrada acabou: sai (e salva)
        }
        std::cin.clear();
        std::string lixo;
        std::getline(std::cin, lixo);
        return -1;  // não era número: opção inválida
    }
    return opcao;
}

void adicionar(std::vector<Contato>& agenda) {
    Contato novo;
    std::cout << "Nome: ";
    std::getline(std::cin >> std::ws, novo.nome);
    std::cout << "Telefone: ";
    std::getline(std::cin >> std::ws, novo.telefone);
    std::cout << "E-mail: ";
    std::getline(std::cin >> std::ws, novo.email);
    agenda.push_back(novo);
    std::cout << "Contato adicionado: " << novo.nome << '\n';
}

// Devolve uma cópia do texto com as letras A-Z trocadas por a-z.
std::string minusculas(std::string texto) {
    for (char& letra : texto) {
        letra = static_cast<char>(std::tolower(static_cast<unsigned char>(letra)));
    }
    return texto;
}

void buscar(const std::vector<Contato>& agenda) {
    std::string busca;
    std::cout << "Buscar por: ";
    std::getline(std::cin >> std::ws, busca);
    const std::string procurado = minusculas(busca);

    int encontrados = 0;
    for (std::size_t i = 0; i < agenda.size(); ++i) {
        if (agenda[i].nome.contains(busca)) {
            mostrar(i + 1, agenda[i]);
            ++encontrados;
        }
    }
    if (encontrados == 0) {
        std::cout << "Nenhum contato encontrado com \"" << busca << "\".\n";
    }
}

int main() {
    std::cout << "=== Agenda da Ana ===\n";
    std::vector<Contato> agenda{
        {"Ana Souza", "(11) 90000-0001", "ana@exemplo.com"},
        {"Bruno Lima", "(21) 90000-0002", "bruno@exemplo.com"},
    };

    int opcao = -1;
    while (opcao != 0) {
        std::cout << "\n1. Adicionar  2. Listar  3. Buscar  0. Sair\n";
        opcao = ler_opcao();
        switch (opcao) {
            case 1: adicionar(agenda); break;
            case 2: listar(agenda); break;
            case 3: buscar(agenda); break;
            case 0: break;
            default: std::cout << "Opção inválida.\n";
        }
    }
    std::cout << "Até logo!\n";
}
#include <cctype>
#include <iostream>
#include <string>
#include <vector>

struct Contato {
    std::string nome;
    std::string telefone;
    std::string email;
};

void mostrar(std::size_t numero, const Contato& c) {
    std::cout << numero << ". " << c.nome << " | " << c.telefone << " | " << c.email << '\n';
}

void listar(const std::vector<Contato>& agenda) {
    if (agenda.empty()) {
        std::cout << "A agenda está vazia.\n";
        return;
    }
    std::cout << "--- Contatos (" << agenda.size() << ") ---\n";
    for (std::size_t i = 0; i < agenda.size(); ++i) {
        mostrar(i + 1, agenda[i]);
    }
}

int ler_opcao() {
    int opcao = -1;
    std::cout << "Opção: ";
    if (!(std::cin >> opcao)) {
        if (std::cin.eof()) {
            return 0;  // a entrada acabou: sai (e salva)
        }
        std::cin.clear();
        std::string lixo;
        std::getline(std::cin, lixo);
        return -1;  // não era número: opção inválida
    }
    return opcao;
}

void adicionar(std::vector<Contato>& agenda) {
    Contato novo;
    std::cout << "Nome: ";
    std::getline(std::cin >> std::ws, novo.nome);
    std::cout << "Telefone: ";
    std::getline(std::cin >> std::ws, novo.telefone);
    std::cout << "E-mail: ";
    std::getline(std::cin >> std::ws, novo.email);
    agenda.push_back(novo);
    std::cout << "Contato adicionado: " << novo.nome << '\n';
}

// Devolve uma cópia do texto com as letras A-Z trocadas por a-z.
std::string minusculas(std::string texto) {
    for (char& letra : texto) {
        letra = static_cast<char>(std::tolower(static_cast<unsigned char>(letra)));
    }
    return texto;
}

void buscar(const std::vector<Contato>& agenda) {
    std::string busca;
    std::cout << "Buscar por: ";
    std::getline(std::cin >> std::ws, busca);
    const std::string procurado = minusculas(busca);

    int encontrados = 0;
    for (std::size_t i = 0; i < agenda.size(); ++i) {
        if (minusculas(agenda[i].nome).contains(procurado)) {
            mostrar(i + 1, agenda[i]);
            ++encontrados;
        }
    }
    if (encontrados == 0) {
        std::cout << "Nenhum contato encontrado com \"" << busca << "\".\n";
    }
}

int main() {
    std::cout << "=== Agenda da Ana ===\n";
    std::vector<Contato> agenda{
        {"Ana Souza", "(11) 90000-0001", "ana@exemplo.com"},
        {"Bruno Lima", "(21) 90000-0002", "bruno@exemplo.com"},
    };

    int opcao = -1;
    while (opcao != 0) {
        std::cout << "\n1. Adicionar  2. Listar  3. Buscar  0. Sair\n";
        opcao = ler_opcao();
        switch (opcao) {
            case 1: adicionar(agenda); break;
            case 2: listar(agenda); break;
            case 3: buscar(agenda); break;
            case 0: break;
            default: std::cout << "Opção inválida.\n";
        }
    }
    std::cout << "Até logo!\n";
}

Etapa 4 · Remover

Para remover, a agenda mostra a lista numerada, pergunta o número e apaga aquele contato. O cuidado principal é com o número: ele precisa existir na lista.

void remover(std::vector<Contato>& agenda) {
    listar(agenda);
    if (agenda.empty()) {
        return;
    }
    std::cout << "Número do contato a remover: ";
    std::size_t numero = 0;
    if (!(std::cin >> numero) || numero < 1 || numero > agenda.size()) {
        std::cin.clear();
        std::string lixo;
        std::getline(std::cin, lixo);
        std::cout << "Número inválido.\n";
        return;
    }
    std::cout << "Removido: " << agenda[numero - 1].nome << '\n';
    agenda.erase(agenda.begin() + (numero - 1));
}

E no switch do main, uma linha nova: case 4: remover(agenda); break;.

if (!(std::cin >> numero) || numero < 1 || numero > agenda.size())
Três motivos para recusar: a leitura falhou (texto), o número é menor que 1, ou é maior que a quantidade de contatos. O || para no primeiro que for verdadeiro.
agenda[numero - 1].nome
O contato número 1 está na posição 0. Pegamos o nome antes de apagar, para mostrar a mensagem.
agenda.erase(agenda.begin() + (numero - 1));
erase ("apagar") remove um elemento do vector, e os seguintes andam uma posição para trás. Ele não recebe uma posição, e sim um iterador: agenda.begin() aponta para o primeiro elemento, e + (numero - 1) anda até o escolhido.
$ g++ -std=c++23 -Wall -Wextra agenda.cpp -o agenda
$ ./agenda
=== Agenda da Ana ===

1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair
Opção: 4
--- Contatos (2) ---
1. Ana Souza | (11) 90000-0001 | ana@exemplo.com
2. Bruno Lima | (21) 90000-0002 | bruno@exemplo.com
Número do contato a remover: 2
Removido: Bruno Lima

1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair
Opção: 2
--- Contatos (1) ---
1. Ana Souza | (11) 90000-0001 | ana@exemplo.com

1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair
Opção: 4
--- Contatos (1) ---
1. Ana Souza | (11) 90000-0001 | ana@exemplo.com
Número do contato a remover: 9
Número inválido.

1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair
Opção: 4
--- Contatos (1) ---
1. Ana Souza | (11) 90000-0001 | ana@exemplo.com
Número do contato a remover: abc
Número inválido.

1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair
Opção: 0
Até logo!
Sem validar o número: o programa aborta

Numa versão sem o if de validação (só std::cin >> numero;), a Ana digitou 9 numa agenda com 2 contatos:

$ ./agenda
=== Agenda da Ana ===

1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair
Opção: 4
--- Contatos (2) ---
1. Ana Souza | (11) 90000-0001 | ana@exemplo.com
2. Bruno Lima | (21) 90000-0002 | bruno@exemplo.com
Número do contato a remover: 9
/usr/include/c++/15/bits/stl_vector.h:1263: constexpr std::vector< <template-parameter-1-1>, <template-parameter-1-2> >::reference std::vector< <template-parameter-1-1>, <template-parameter-1-2> >::operator[](size_type) [with _Tp = Contato; _Alloc = std::allocator<Contato>; reference = Contato&; size_type = long unsigned int]: Assertion '__n < this->size()' failed.
Abortado                   (imagem do núcleo gravada) ./agenda
$ echo $?
134

O que significa: o agenda[numero - 1] tentou ler a posição 8 de uma lista com 2 elementos. A mensagem enorme vem de dentro do vector (stl_vector.h), e o que importa está no fim: Assertion '__n < this->size()' failed, "a verificação 'posição menor que o tamanho' falhou". O g++ do Ubuntu liga essa verificação por padrão, e o programa é abortado na hora (código de saída 134). Em outros compiladores ou configurações, a verificação pode não existir: aí é comportamento indefinido, e o programa pode mostrar lixo ou apagar o contato errado.

Como consertar: confira o número antes de usá-lo, como no código da etapa. Os contatos que a Ana tinha digitado nessa sessão foram perdidos: mais um motivo para a etapa 5.

Missão (etapa 4): a função remover já mostra a lista, lê e valida o número. Complete o final: mostre Removido: com o nome do contato e apague-o com agenda.erase(agenda.begin() + (numero - 1));. A entrada remove o contato 2 e lista.

#include <cctype>
#include <iostream>
#include <string>
#include <vector>

struct Contato {
    std::string nome;
    std::string telefone;
    std::string email;
};

void mostrar(std::size_t numero, const Contato& c) {
    std::cout << numero << ". " << c.nome << " | " << c.telefone << " | " << c.email << '\n';
}

void listar(const std::vector<Contato>& agenda) {
    if (agenda.empty()) {
        std::cout << "A agenda está vazia.\n";
        return;
    }
    std::cout << "--- Contatos (" << agenda.size() << ") ---\n";
    for (std::size_t i = 0; i < agenda.size(); ++i) {
        mostrar(i + 1, agenda[i]);
    }
}

int ler_opcao() {
    int opcao = -1;
    std::cout << "Opção: ";
    if (!(std::cin >> opcao)) {
        if (std::cin.eof()) {
            return 0;  // a entrada acabou: sai (e salva)
        }
        std::cin.clear();
        std::string lixo;
        std::getline(std::cin, lixo);
        return -1;  // não era número: opção inválida
    }
    return opcao;
}

void adicionar(std::vector<Contato>& agenda) {
    Contato novo;
    std::cout << "Nome: ";
    std::getline(std::cin >> std::ws, novo.nome);
    std::cout << "Telefone: ";
    std::getline(std::cin >> std::ws, novo.telefone);
    std::cout << "E-mail: ";
    std::getline(std::cin >> std::ws, novo.email);
    agenda.push_back(novo);
    std::cout << "Contato adicionado: " << novo.nome << '\n';
}

// Devolve uma cópia do texto com as letras A-Z trocadas por a-z.
std::string minusculas(std::string texto) {
    for (char& letra : texto) {
        letra = static_cast<char>(std::tolower(static_cast<unsigned char>(letra)));
    }
    return texto;
}

void buscar(const std::vector<Contato>& agenda) {
    std::string busca;
    std::cout << "Buscar por: ";
    std::getline(std::cin >> std::ws, busca);
    const std::string procurado = minusculas(busca);

    int encontrados = 0;
    for (std::size_t i = 0; i < agenda.size(); ++i) {
        if (minusculas(agenda[i].nome).contains(procurado)) {
            mostrar(i + 1, agenda[i]);
            ++encontrados;
        }
    }
    if (encontrados == 0) {
        std::cout << "Nenhum contato encontrado com \"" << busca << "\".\n";
    }
}

void remover(std::vector<Contato>& agenda) {
    listar(agenda);
    if (agenda.empty()) {
        return;
    }
    std::cout << "Número do contato a remover: ";
    std::size_t numero = 0;
    if (!(std::cin >> numero) || numero < 1 || numero > agenda.size()) {
        std::cin.clear();
        std::string lixo;
        std::getline(std::cin, lixo);
        std::cout << "Número inválido.\n";
        return;
    }
    // mostre "Removido: " e o nome, e apague o contato da agenda
}

int main() {
    std::cout << "=== Agenda da Ana ===\n";
    std::vector<Contato> agenda{
        {"Ana Souza", "(11) 90000-0001", "ana@exemplo.com"},
        {"Bruno Lima", "(21) 90000-0002", "bruno@exemplo.com"},
    };

    int opcao = -1;
    while (opcao != 0) {
        std::cout << "\n1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair\n";
        opcao = ler_opcao();
        switch (opcao) {
            case 1: adicionar(agenda); break;
            case 2: listar(agenda); break;
            case 3: buscar(agenda); break;
            case 4: remover(agenda); break;
            case 0: break;
            default: std::cout << "Opção inválida.\n";
        }
    }
    std::cout << "Até logo!\n";
}
#include <cctype>
#include <iostream>
#include <string>
#include <vector>

struct Contato {
    std::string nome;
    std::string telefone;
    std::string email;
};

void mostrar(std::size_t numero, const Contato& c) {
    std::cout << numero << ". " << c.nome << " | " << c.telefone << " | " << c.email << '\n';
}

void listar(const std::vector<Contato>& agenda) {
    if (agenda.empty()) {
        std::cout << "A agenda está vazia.\n";
        return;
    }
    std::cout << "--- Contatos (" << agenda.size() << ") ---\n";
    for (std::size_t i = 0; i < agenda.size(); ++i) {
        mostrar(i + 1, agenda[i]);
    }
}

int ler_opcao() {
    int opcao = -1;
    std::cout << "Opção: ";
    if (!(std::cin >> opcao)) {
        if (std::cin.eof()) {
            return 0;  // a entrada acabou: sai (e salva)
        }
        std::cin.clear();
        std::string lixo;
        std::getline(std::cin, lixo);
        return -1;  // não era número: opção inválida
    }
    return opcao;
}

void adicionar(std::vector<Contato>& agenda) {
    Contato novo;
    std::cout << "Nome: ";
    std::getline(std::cin >> std::ws, novo.nome);
    std::cout << "Telefone: ";
    std::getline(std::cin >> std::ws, novo.telefone);
    std::cout << "E-mail: ";
    std::getline(std::cin >> std::ws, novo.email);
    agenda.push_back(novo);
    std::cout << "Contato adicionado: " << novo.nome << '\n';
}

// Devolve uma cópia do texto com as letras A-Z trocadas por a-z.
std::string minusculas(std::string texto) {
    for (char& letra : texto) {
        letra = static_cast<char>(std::tolower(static_cast<unsigned char>(letra)));
    }
    return texto;
}

void buscar(const std::vector<Contato>& agenda) {
    std::string busca;
    std::cout << "Buscar por: ";
    std::getline(std::cin >> std::ws, busca);
    const std::string procurado = minusculas(busca);

    int encontrados = 0;
    for (std::size_t i = 0; i < agenda.size(); ++i) {
        if (minusculas(agenda[i].nome).contains(procurado)) {
            mostrar(i + 1, agenda[i]);
            ++encontrados;
        }
    }
    if (encontrados == 0) {
        std::cout << "Nenhum contato encontrado com \"" << busca << "\".\n";
    }
}

void remover(std::vector<Contato>& agenda) {
    listar(agenda);
    if (agenda.empty()) {
        return;
    }
    std::cout << "Número do contato a remover: ";
    std::size_t numero = 0;
    if (!(std::cin >> numero) || numero < 1 || numero > agenda.size()) {
        std::cin.clear();
        std::string lixo;
        std::getline(std::cin, lixo);
        std::cout << "Número inválido.\n";
        return;
    }
    std::cout << "Removido: " << agenda[numero - 1].nome << '\n';
    agenda.erase(agenda.begin() + (numero - 1));
}

int main() {
    std::cout << "=== Agenda da Ana ===\n";
    std::vector<Contato> agenda{
        {"Ana Souza", "(11) 90000-0001", "ana@exemplo.com"},
        {"Bruno Lima", "(21) 90000-0002", "bruno@exemplo.com"},
    };

    int opcao = -1;
    while (opcao != 0) {
        std::cout << "\n1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair\n";
        opcao = ler_opcao();
        switch (opcao) {
            case 1: adicionar(agenda); break;
            case 2: listar(agenda); break;
            case 3: buscar(agenda); break;
            case 4: remover(agenda); break;
            case 0: break;
            default: std::cout << "Opção inválida.\n";
        }
    }
    std::cout << "Até logo!\n";
}

Etapa 5 · Salvar no arquivo

Até agora, tudo o que a Ana digita some quando o programa fecha: as variáveis vivem na memória, e a memória do programa é devolvida ao sistema no fim. Para guardar de verdade, os contatos precisam ir para um arquivo no disco.

Primeiro, decidimos o formato do arquivo. O mais simples: um contato por linha, com os campos separados por ponto e vírgula. É parecido com um arquivo CSV (o formato das planilhas), e dá para abrir e ler num editor de texto:

Ana Souza;(11) 90000-0001;ana@exemplo.com
Bruno Lima;(21) 90000-0002;bruno@exemplo.com

Por que ponto e vírgula e não vírgula? Porque vírgula aparece em nomes e endereços com mais frequência. Para gravar, usamos um std::ofstream (de output file stream, "fluxo de saída para arquivo"), que funciona como o cout, mas escreve num arquivo (veja Ler e Escrever Arquivos):

#include <fstream>   // novo: ofstream e ifstream

// ...

const std::string arquivo_agenda = "agenda.txt";

void salvar(const std::vector<Contato>& agenda) {
    std::ofstream arquivo(arquivo_agenda);
    if (!arquivo) {
        std::cout << "Não consegui abrir " << arquivo_agenda << " para gravar.\n";
        return;
    }
    for (const Contato& c : agenda) {
        arquivo << c.nome << ';' << c.telefone << ';' << c.email << '\n';
    }
    std::cout << "Agenda salva em " << arquivo_agenda << " (" << agenda.size() << " contatos).\n";
}

E no main, a opção 0 passa a salvar antes de sair: case 0: salvar(agenda); break;.

#include <fstream>
O cabeçalho dos arquivos: std::ofstream (escrever) e std::ifstream (ler).
const std::string arquivo_agenda = "agenda.txt";
O nome do arquivo numa constante, fora das funções: salvar e (na próxima etapa) carregar usam o mesmo nome, escrito num lugar só. O arquivo fica na pasta onde o programa roda.
std::ofstream arquivo(arquivo_agenda);
Abre o arquivo para escrita. Se ele não existe, é criado; se existe, é apagado e reescrito do zero. Como sempre gravamos a agenda inteira, é exatamente o que queremos.
if (!arquivo)
Confere se abriu. Pode falhar se a pasta não deixar gravar, por exemplo.
arquivo << c.nome << ';' << …
Escreve como no cout: nome, ;, telefone, ;, e-mail e a quebra de linha.
(fim da função)
Não há close(): quando a variável arquivo sai de escopo, no fim da função, o ofstream fecha o arquivo sozinho. É o RAII (veja Destrutores e RAII).
$ g++ -std=c++23 -Wall -Wextra agenda.cpp -o agenda
$ ./agenda
=== Agenda da Ana ===

1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair
Opção: 1
Nome: Carla Dias
Telefone: (31) 90000-0003
E-mail: carla@exemplo.com
Contato adicionado: Carla Dias

1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair
Opção: 0
Agenda salva em agenda.txt (3 contatos).
Até logo!
$ cat agenda.txt                « o cat mostra o conteúdo do arquivo
Ana Souza;(11) 90000-0001;ana@exemplo.com
Bruno Lima;(21) 90000-0002;bruno@exemplo.com
Carla Dias;(31) 90000-0003;carla@exemplo.com
Esqueceu o #include <fstream>
$ g++ -std=c++23 -Wall -Wextra agenda.cpp -o agenda
agenda.cpp: In function ‘void salvar(const std::vector<Contato>&)’:
agenda.cpp:101:41: error: variable ‘std::ofstream arquivo’ has initializer but incomplete type
  101 |     std::ofstream arquivo(arquivo_agenda);
      |                                         ^
agenda.cpp:5:1: note: ‘std::ofstream’ is defined in header ‘<fstream>’; this is probably fixable by adding ‘#include <fstream>’
    4 | #include <vector>
  +++ |+#include <fstream>
    5 | 

O que significa: "a variável arquivo tem valor inicial, mas o tipo está incompleto": o compilador sabe que std::ofstream existe (o <iostream> o anuncia), mas não sabe como ele é por dentro. A nota é ótima: diz que std::ofstream é definido em <fstream> e até mostra, com +++ |+, onde colocar a linha.

Como consertar: #include <fstream>.

Missão (etapa 5): a função salvar está vazia. Abra o arquivo com std::ofstream, grave cada contato numa linha no formato nome;telefone;email e mostre Agenda salva em agenda.txt (3 contatos). (o 3 vem de agenda.size()). A entrada adiciona a Carla e escolhe 0.

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

struct Contato {
    std::string nome;
    std::string telefone;
    std::string email;
};

const std::string arquivo_agenda = "agenda.txt";

void mostrar(std::size_t numero, const Contato& c) {
    std::cout << numero << ". " << c.nome << " | " << c.telefone << " | " << c.email << '\n';
}

void listar(const std::vector<Contato>& agenda) {
    if (agenda.empty()) {
        std::cout << "A agenda está vazia.\n";
        return;
    }
    std::cout << "--- Contatos (" << agenda.size() << ") ---\n";
    for (std::size_t i = 0; i < agenda.size(); ++i) {
        mostrar(i + 1, agenda[i]);
    }
}

int ler_opcao() {
    int opcao = -1;
    std::cout << "Opção: ";
    if (!(std::cin >> opcao)) {
        if (std::cin.eof()) {
            return 0;  // a entrada acabou: sai (e salva)
        }
        std::cin.clear();
        std::string lixo;
        std::getline(std::cin, lixo);
        return -1;  // não era número: opção inválida
    }
    return opcao;
}

void adicionar(std::vector<Contato>& agenda) {
    Contato novo;
    std::cout << "Nome: ";
    std::getline(std::cin >> std::ws, novo.nome);
    std::cout << "Telefone: ";
    std::getline(std::cin >> std::ws, novo.telefone);
    std::cout << "E-mail: ";
    std::getline(std::cin >> std::ws, novo.email);
    agenda.push_back(novo);
    std::cout << "Contato adicionado: " << novo.nome << '\n';
}

// Devolve uma cópia do texto com as letras A-Z trocadas por a-z.
std::string minusculas(std::string texto) {
    for (char& letra : texto) {
        letra = static_cast<char>(std::tolower(static_cast<unsigned char>(letra)));
    }
    return texto;
}

void buscar(const std::vector<Contato>& agenda) {
    std::string busca;
    std::cout << "Buscar por: ";
    std::getline(std::cin >> std::ws, busca);
    const std::string procurado = minusculas(busca);

    int encontrados = 0;
    for (std::size_t i = 0; i < agenda.size(); ++i) {
        if (minusculas(agenda[i].nome).contains(procurado)) {
            mostrar(i + 1, agenda[i]);
            ++encontrados;
        }
    }
    if (encontrados == 0) {
        std::cout << "Nenhum contato encontrado com \"" << busca << "\".\n";
    }
}

void remover(std::vector<Contato>& agenda) {
    listar(agenda);
    if (agenda.empty()) {
        return;
    }
    std::cout << "Número do contato a remover: ";
    std::size_t numero = 0;
    if (!(std::cin >> numero) || numero < 1 || numero > agenda.size()) {
        std::cin.clear();
        std::string lixo;
        std::getline(std::cin, lixo);
        std::cout << "Número inválido.\n";
        return;
    }
    std::cout << "Removido: " << agenda[numero - 1].nome << '\n';
    agenda.erase(agenda.begin() + (numero - 1));
}

void salvar(const std::vector<Contato>& agenda) {
    // grave cada contato numa linha do arquivo, no formato nome;telefone;email
    // e mostre: Agenda salva em agenda.txt (N contatos).
}

int main() {
    std::cout << "=== Agenda da Ana ===\n";
    std::vector<Contato> agenda{
        {"Ana Souza", "(11) 90000-0001", "ana@exemplo.com"},
        {"Bruno Lima", "(21) 90000-0002", "bruno@exemplo.com"},
    };

    int opcao = -1;
    while (opcao != 0) {
        std::cout << "\n1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair\n";
        opcao = ler_opcao();
        switch (opcao) {
            case 1: adicionar(agenda); break;
            case 2: listar(agenda); break;
            case 3: buscar(agenda); break;
            case 4: remover(agenda); break;
            case 0: salvar(agenda); break;
            default: std::cout << "Opção inválida.\n";
        }
    }
    std::cout << "Até logo!\n";
}
#include <cctype>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>

struct Contato {
    std::string nome;
    std::string telefone;
    std::string email;
};

const std::string arquivo_agenda = "agenda.txt";

void mostrar(std::size_t numero, const Contato& c) {
    std::cout << numero << ". " << c.nome << " | " << c.telefone << " | " << c.email << '\n';
}

void listar(const std::vector<Contato>& agenda) {
    if (agenda.empty()) {
        std::cout << "A agenda está vazia.\n";
        return;
    }
    std::cout << "--- Contatos (" << agenda.size() << ") ---\n";
    for (std::size_t i = 0; i < agenda.size(); ++i) {
        mostrar(i + 1, agenda[i]);
    }
}

int ler_opcao() {
    int opcao = -1;
    std::cout << "Opção: ";
    if (!(std::cin >> opcao)) {
        if (std::cin.eof()) {
            return 0;  // a entrada acabou: sai (e salva)
        }
        std::cin.clear();
        std::string lixo;
        std::getline(std::cin, lixo);
        return -1;  // não era número: opção inválida
    }
    return opcao;
}

void adicionar(std::vector<Contato>& agenda) {
    Contato novo;
    std::cout << "Nome: ";
    std::getline(std::cin >> std::ws, novo.nome);
    std::cout << "Telefone: ";
    std::getline(std::cin >> std::ws, novo.telefone);
    std::cout << "E-mail: ";
    std::getline(std::cin >> std::ws, novo.email);
    agenda.push_back(novo);
    std::cout << "Contato adicionado: " << novo.nome << '\n';
}

// Devolve uma cópia do texto com as letras A-Z trocadas por a-z.
std::string minusculas(std::string texto) {
    for (char& letra : texto) {
        letra = static_cast<char>(std::tolower(static_cast<unsigned char>(letra)));
    }
    return texto;
}

void buscar(const std::vector<Contato>& agenda) {
    std::string busca;
    std::cout << "Buscar por: ";
    std::getline(std::cin >> std::ws, busca);
    const std::string procurado = minusculas(busca);

    int encontrados = 0;
    for (std::size_t i = 0; i < agenda.size(); ++i) {
        if (minusculas(agenda[i].nome).contains(procurado)) {
            mostrar(i + 1, agenda[i]);
            ++encontrados;
        }
    }
    if (encontrados == 0) {
        std::cout << "Nenhum contato encontrado com \"" << busca << "\".\n";
    }
}

void remover(std::vector<Contato>& agenda) {
    listar(agenda);
    if (agenda.empty()) {
        return;
    }
    std::cout << "Número do contato a remover: ";
    std::size_t numero = 0;
    if (!(std::cin >> numero) || numero < 1 || numero > agenda.size()) {
        std::cin.clear();
        std::string lixo;
        std::getline(std::cin, lixo);
        std::cout << "Número inválido.\n";
        return;
    }
    std::cout << "Removido: " << agenda[numero - 1].nome << '\n';
    agenda.erase(agenda.begin() + (numero - 1));
}

void salvar(const std::vector<Contato>& agenda) {
    std::ofstream arquivo(arquivo_agenda);
    if (!arquivo) {
        std::cout << "Não consegui abrir " << arquivo_agenda << " para gravar.\n";
        return;
    }
    for (const Contato& c : agenda) {
        arquivo << c.nome << ';' << c.telefone << ';' << c.email << '\n';
    }
    std::cout << "Agenda salva em " << arquivo_agenda << " (" << agenda.size() << " contatos).\n";
}

int main() {
    std::cout << "=== Agenda da Ana ===\n";
    std::vector<Contato> agenda{
        {"Ana Souza", "(11) 90000-0001", "ana@exemplo.com"},
        {"Bruno Lima", "(21) 90000-0002", "bruno@exemplo.com"},
    };

    int opcao = -1;
    while (opcao != 0) {
        std::cout << "\n1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair\n";
        opcao = ler_opcao();
        switch (opcao) {
            case 1: adicionar(agenda); break;
            case 2: listar(agenda); break;
            case 3: buscar(agenda); break;
            case 4: remover(agenda); break;
            case 0: salvar(agenda); break;
            default: std::cout << "Opção inválida.\n";
        }
    }
    std::cout << "Até logo!\n";
}

Etapa 6 · Carregar do arquivo

O arquivo já é gravado; falta lê-lo quando o programa abre. A lista inicial com a Ana e o Bruno escrita no código sai de cena: a agenda começa com o que estiver no arquivo. Para ler, um std::ifstream (input file stream, "fluxo de entrada de arquivo"), que funciona como o cin. E para separar os campos de cada linha, um truque: transformar a linha num "mini cin" com std::istringstream e usar o getline com o ; como separador.

#include <sstream>   // novo: istringstream

// ...

std::vector<Contato> carregar() {
    std::vector<Contato> agenda;
    std::ifstream arquivo(arquivo_agenda);
    if (!arquivo) {
        std::cout << "Nenhuma agenda salva ainda (" << arquivo_agenda << " não existe).\n";
        return agenda;
    }
    std::string linha;
    while (std::getline(arquivo, linha)) {
        std::istringstream campos(linha);
        Contato c;
        std::getline(campos, c.nome, ';');
        std::getline(campos, c.telefone, ';');
        std::getline(campos, c.email);
        if (!c.nome.empty()) {
            agenda.push_back(c);
        }
    }
    std::cout << "Carregados " << agenda.size() << " contatos de " << arquivo_agenda << ".\n";
    return agenda;
}

int main() {
    std::cout << "=== Agenda da Ana ===\n";
    std::vector<Contato> agenda = carregar();

    // ... (o resto do main não muda)
std::ifstream arquivo(arquivo_agenda); if (!arquivo)
Abre para leitura. Na primeira vez que a agenda roda, o arquivo ainda não existe e a abertura falha. Isso não é um erro: só avisamos e devolvemos a agenda vazia.
while (std::getline(arquivo, linha))
Lê o arquivo linha por linha. O getline devolve "falso" quando o arquivo acaba, e o laço termina.
std::istringstream campos(linha);
Um fluxo de entrada que lê de uma std::string em vez do teclado (vem do <sstream>; veja Texto ↔ Número). Assim podemos usar getline dentro da linha.
std::getline(campos, c.nome, ';');
O terceiro argumento muda o separador: em vez de ler até o fim da linha, lê até o ;. Primeiro o nome, depois o telefone…
std::getline(campos, c.email);
…e o e-mail, que é o resto da linha.
if (!c.nome.empty())
Pula linhas vazias (por exemplo, uma linha em branco no fim do arquivo, se alguém o editar à mão).
std::vector<Contato> agenda = carregar();
No main, a agenda nasce com o que a função devolveu.

A primeira vez, sem arquivo, e a segunda vez, com o arquivo gravado pela primeira:

$ g++ -std=c++23 -Wall -Wextra agenda.cpp -o agenda
$ rm agenda.txt                 « apaga o arquivo da etapa 5, para começar do zero
$ ./agenda
=== Agenda da Ana ===
Nenhuma agenda salva ainda (agenda.txt não existe).

1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair
Opção: 1
Nome: Ana Souza
Telefone: (11) 90000-0001
E-mail: ana@exemplo.com
Contato adicionado: Ana Souza

1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair
Opção: 1
Nome: Bruno Lima
Telefone: (21) 90000-0002
E-mail: bruno@exemplo.com
Contato adicionado: Bruno Lima

1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair
Opção: 0
Agenda salva em agenda.txt (2 contatos).
Até logo!
$ ./agenda
=== Agenda da Ana ===
Carregados 2 contatos de agenda.txt.  « os contatos sobreviveram ao fim do programa

1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair
Opção: 2
--- Contatos (2) ---
1. Ana Souza | (11) 90000-0001 | ana@exemplo.com
2. Bruno Lima | (21) 90000-0002 | bruno@exemplo.com

1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair
Opção: 0
Agenda salva em agenda.txt (2 contatos).
Até logo!
Um ; dentro do nome bagunça o arquivo

A Ana cadastrou um cliente como Dias; Carla (sobrenome primeiro). A agenda salvou sem reclamar, mas na próxima abertura:

$ cat agenda.txt
Ana Souza;(11) 90000-0001;ana@exemplo.com
Bruno Lima;(21) 90000-0002;bruno@exemplo.com
Dias; Carla;(31) 90000-0003;carla@exemplo.com
$ ./agenda | head -9           « (a Ana escolheu 2 e depois 0)
=== Agenda da Ana ===
Carregados 3 contatos de agenda.txt.

1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair
Opção: 2
--- Contatos (3) ---
1. Ana Souza | (11) 90000-0001 | ana@exemplo.com
2. Bruno Lima | (21) 90000-0002 | bruno@exemplo.com
3. Dias |  Carla | (31) 90000-0003;carla@exemplo.com

O que significa: o ; do nome foi confundido com o separador. O nome virou Dias, o telefone virou Carla, e o resto da linha caiu no e-mail. Todo formato com separador tem esse problema: o separador não pode aparecer dentro dos dados.

Como consertar: no adicionar, recuse (ou troque) o ; nos campos. É o desafio 2 abaixo. Formatos profissionais, como o CSV de verdade e o JSON, resolvem isso com aspas e "escapes" (veja Caracteres Especiais).

Missão (etapa 6): o arquivo agenda.txt (na segunda aba) já tem três contatos, mas a função carregar só devolve uma agenda vazia. Escreva a leitura: abra com std::ifstream, leia cada linha com getline, separe os campos com std::istringstream e getline(campos, …, ';'), e mostre Carregados 3 contatos de agenda.txt.. A entrada lista e sai.

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

struct Contato {
    std::string nome;
    std::string telefone;
    std::string email;
};

const std::string arquivo_agenda = "agenda.txt";

void mostrar(std::size_t numero, const Contato& c) {
    std::cout << numero << ". " << c.nome << " | " << c.telefone << " | " << c.email << '\n';
}

void listar(const std::vector<Contato>& agenda) {
    if (agenda.empty()) {
        std::cout << "A agenda está vazia.\n";
        return;
    }
    std::cout << "--- Contatos (" << agenda.size() << ") ---\n";
    for (std::size_t i = 0; i < agenda.size(); ++i) {
        mostrar(i + 1, agenda[i]);
    }
}

int ler_opcao() {
    int opcao = -1;
    std::cout << "Opção: ";
    if (!(std::cin >> opcao)) {
        if (std::cin.eof()) {
            return 0;  // a entrada acabou: sai (e salva)
        }
        std::cin.clear();
        std::string lixo;
        std::getline(std::cin, lixo);
        return -1;  // não era número: opção inválida
    }
    return opcao;
}

void adicionar(std::vector<Contato>& agenda) {
    Contato novo;
    std::cout << "Nome: ";
    std::getline(std::cin >> std::ws, novo.nome);
    std::cout << "Telefone: ";
    std::getline(std::cin >> std::ws, novo.telefone);
    std::cout << "E-mail: ";
    std::getline(std::cin >> std::ws, novo.email);
    agenda.push_back(novo);
    std::cout << "Contato adicionado: " << novo.nome << '\n';
}

// Devolve uma cópia do texto com as letras A-Z trocadas por a-z.
std::string minusculas(std::string texto) {
    for (char& letra : texto) {
        letra = static_cast<char>(std::tolower(static_cast<unsigned char>(letra)));
    }
    return texto;
}

void buscar(const std::vector<Contato>& agenda) {
    std::string busca;
    std::cout << "Buscar por: ";
    std::getline(std::cin >> std::ws, busca);
    const std::string procurado = minusculas(busca);

    int encontrados = 0;
    for (std::size_t i = 0; i < agenda.size(); ++i) {
        if (minusculas(agenda[i].nome).contains(procurado)) {
            mostrar(i + 1, agenda[i]);
            ++encontrados;
        }
    }
    if (encontrados == 0) {
        std::cout << "Nenhum contato encontrado com \"" << busca << "\".\n";
    }
}

void remover(std::vector<Contato>& agenda) {
    listar(agenda);
    if (agenda.empty()) {
        return;
    }
    std::cout << "Número do contato a remover: ";
    std::size_t numero = 0;
    if (!(std::cin >> numero) || numero < 1 || numero > agenda.size()) {
        std::cin.clear();
        std::string lixo;
        std::getline(std::cin, lixo);
        std::cout << "Número inválido.\n";
        return;
    }
    std::cout << "Removido: " << agenda[numero - 1].nome << '\n';
    agenda.erase(agenda.begin() + (numero - 1));
}

void salvar(const std::vector<Contato>& agenda) {
    std::ofstream arquivo(arquivo_agenda);
    if (!arquivo) {
        std::cout << "Não consegui abrir " << arquivo_agenda << " para gravar.\n";
        return;
    }
    for (const Contato& c : agenda) {
        arquivo << c.nome << ';' << c.telefone << ';' << c.email << '\n';
    }
    std::cout << "Agenda salva em " << arquivo_agenda << " (" << agenda.size() << " contatos).\n";
}

std::vector<Contato> carregar() {
    std::vector<Contato> agenda;
    // abra o arquivo, leia linha por linha, separe os campos pelo ';'
    // e mostre: Carregados N contatos de agenda.txt.
    return agenda;
}

int main() {
    std::cout << "=== Agenda da Ana ===\n";
    std::vector<Contato> agenda = carregar();

    int opcao = -1;
    while (opcao != 0) {
        std::cout << "\n1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair\n";
        opcao = ler_opcao();
        switch (opcao) {
            case 1: adicionar(agenda); break;
            case 2: listar(agenda); break;
            case 3: buscar(agenda); break;
            case 4: remover(agenda); break;
            case 0: salvar(agenda); break;
            default: std::cout << "Opção inválida.\n";
        }
    }
    std::cout << "Até logo!\n";
}
Ana Souza;(11) 90000-0001;ana@exemplo.com
Bruno Lima;(21) 90000-0002;bruno@exemplo.com
Carla Dias;(31) 90000-0003;carla@exemplo.com
#include <cctype>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

struct Contato {
    std::string nome;
    std::string telefone;
    std::string email;
};

const std::string arquivo_agenda = "agenda.txt";

void mostrar(std::size_t numero, const Contato& c) {
    std::cout << numero << ". " << c.nome << " | " << c.telefone << " | " << c.email << '\n';
}

void listar(const std::vector<Contato>& agenda) {
    if (agenda.empty()) {
        std::cout << "A agenda está vazia.\n";
        return;
    }
    std::cout << "--- Contatos (" << agenda.size() << ") ---\n";
    for (std::size_t i = 0; i < agenda.size(); ++i) {
        mostrar(i + 1, agenda[i]);
    }
}

int ler_opcao() {
    int opcao = -1;
    std::cout << "Opção: ";
    if (!(std::cin >> opcao)) {
        if (std::cin.eof()) {
            return 0;  // a entrada acabou: sai (e salva)
        }
        std::cin.clear();
        std::string lixo;
        std::getline(std::cin, lixo);
        return -1;  // não era número: opção inválida
    }
    return opcao;
}

void adicionar(std::vector<Contato>& agenda) {
    Contato novo;
    std::cout << "Nome: ";
    std::getline(std::cin >> std::ws, novo.nome);
    std::cout << "Telefone: ";
    std::getline(std::cin >> std::ws, novo.telefone);
    std::cout << "E-mail: ";
    std::getline(std::cin >> std::ws, novo.email);
    agenda.push_back(novo);
    std::cout << "Contato adicionado: " << novo.nome << '\n';
}

// Devolve uma cópia do texto com as letras A-Z trocadas por a-z.
std::string minusculas(std::string texto) {
    for (char& letra : texto) {
        letra = static_cast<char>(std::tolower(static_cast<unsigned char>(letra)));
    }
    return texto;
}

void buscar(const std::vector<Contato>& agenda) {
    std::string busca;
    std::cout << "Buscar por: ";
    std::getline(std::cin >> std::ws, busca);
    const std::string procurado = minusculas(busca);

    int encontrados = 0;
    for (std::size_t i = 0; i < agenda.size(); ++i) {
        if (minusculas(agenda[i].nome).contains(procurado)) {
            mostrar(i + 1, agenda[i]);
            ++encontrados;
        }
    }
    if (encontrados == 0) {
        std::cout << "Nenhum contato encontrado com \"" << busca << "\".\n";
    }
}

void remover(std::vector<Contato>& agenda) {
    listar(agenda);
    if (agenda.empty()) {
        return;
    }
    std::cout << "Número do contato a remover: ";
    std::size_t numero = 0;
    if (!(std::cin >> numero) || numero < 1 || numero > agenda.size()) {
        std::cin.clear();
        std::string lixo;
        std::getline(std::cin, lixo);
        std::cout << "Número inválido.\n";
        return;
    }
    std::cout << "Removido: " << agenda[numero - 1].nome << '\n';
    agenda.erase(agenda.begin() + (numero - 1));
}

void salvar(const std::vector<Contato>& agenda) {
    std::ofstream arquivo(arquivo_agenda);
    if (!arquivo) {
        std::cout << "Não consegui abrir " << arquivo_agenda << " para gravar.\n";
        return;
    }
    for (const Contato& c : agenda) {
        arquivo << c.nome << ';' << c.telefone << ';' << c.email << '\n';
    }
    std::cout << "Agenda salva em " << arquivo_agenda << " (" << agenda.size() << " contatos).\n";
}

std::vector<Contato> carregar() {
    std::vector<Contato> agenda;
    std::ifstream arquivo(arquivo_agenda);
    if (!arquivo) {
        std::cout << "Nenhuma agenda salva ainda (" << arquivo_agenda << " não existe).\n";
        return agenda;
    }
    std::string linha;
    while (std::getline(arquivo, linha)) {
        std::istringstream campos(linha);
        Contato c;
        std::getline(campos, c.nome, ';');
        std::getline(campos, c.telefone, ';');
        std::getline(campos, c.email);
        if (!c.nome.empty()) {
            agenda.push_back(c);
        }
    }
    std::cout << "Carregados " << agenda.size() << " contatos de " << arquivo_agenda << ".\n";
    return agenda;
}

int main() {
    std::cout << "=== Agenda da Ana ===\n";
    std::vector<Contato> agenda = carregar();

    int opcao = -1;
    while (opcao != 0) {
        std::cout << "\n1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair\n";
        opcao = ler_opcao();
        switch (opcao) {
            case 1: adicionar(agenda); break;
            case 2: listar(agenda); break;
            case 3: buscar(agenda); break;
            case 4: remover(agenda); break;
            case 0: salvar(agenda); break;
            default: std::cout << "Opção inválida.\n";
        }
    }
    std::cout << "Até logo!\n";
}

O código completo

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

struct Contato {
    std::string nome;
    std::string telefone;
    std::string email;
};

const std::string arquivo_agenda = "agenda.txt";

void mostrar(std::size_t numero, const Contato& c) {
    std::cout << numero << ". " << c.nome << " | " << c.telefone << " | " << c.email << '\n';
}

void listar(const std::vector<Contato>& agenda) {
    if (agenda.empty()) {
        std::cout << "A agenda está vazia.\n";
        return;
    }
    std::cout << "--- Contatos (" << agenda.size() << ") ---\n";
    for (std::size_t i = 0; i < agenda.size(); ++i) {
        mostrar(i + 1, agenda[i]);
    }
}

int ler_opcao() {
    int opcao = -1;
    std::cout << "Opção: ";
    if (!(std::cin >> opcao)) {
        if (std::cin.eof()) {
            return 0;  // a entrada acabou: sai (e salva)
        }
        std::cin.clear();
        std::string lixo;
        std::getline(std::cin, lixo);
        return -1;  // não era número: opção inválida
    }
    return opcao;
}

void adicionar(std::vector<Contato>& agenda) {
    Contato novo;
    std::cout << "Nome: ";
    std::getline(std::cin >> std::ws, novo.nome);
    std::cout << "Telefone: ";
    std::getline(std::cin >> std::ws, novo.telefone);
    std::cout << "E-mail: ";
    std::getline(std::cin >> std::ws, novo.email);
    agenda.push_back(novo);
    std::cout << "Contato adicionado: " << novo.nome << '\n';
}

// Devolve uma cópia do texto com as letras A-Z trocadas por a-z.
std::string minusculas(std::string texto) {
    for (char& letra : texto) {
        letra = static_cast<char>(std::tolower(static_cast<unsigned char>(letra)));
    }
    return texto;
}

void buscar(const std::vector<Contato>& agenda) {
    std::string busca;
    std::cout << "Buscar por: ";
    std::getline(std::cin >> std::ws, busca);
    const std::string procurado = minusculas(busca);

    int encontrados = 0;
    for (std::size_t i = 0; i < agenda.size(); ++i) {
        if (minusculas(agenda[i].nome).contains(procurado)) {
            mostrar(i + 1, agenda[i]);
            ++encontrados;
        }
    }
    if (encontrados == 0) {
        std::cout << "Nenhum contato encontrado com \"" << busca << "\".\n";
    }
}

void remover(std::vector<Contato>& agenda) {
    listar(agenda);
    if (agenda.empty()) {
        return;
    }
    std::cout << "Número do contato a remover: ";
    std::size_t numero = 0;
    if (!(std::cin >> numero) || numero < 1 || numero > agenda.size()) {
        std::cin.clear();
        std::string lixo;
        std::getline(std::cin, lixo);
        std::cout << "Número inválido.\n";
        return;
    }
    std::cout << "Removido: " << agenda[numero - 1].nome << '\n';
    agenda.erase(agenda.begin() + (numero - 1));
}

void salvar(const std::vector<Contato>& agenda) {
    std::ofstream arquivo(arquivo_agenda);
    if (!arquivo) {
        std::cout << "Não consegui abrir " << arquivo_agenda << " para gravar.\n";
        return;
    }
    for (const Contato& c : agenda) {
        arquivo << c.nome << ';' << c.telefone << ';' << c.email << '\n';
    }
    std::cout << "Agenda salva em " << arquivo_agenda << " (" << agenda.size() << " contatos).\n";
}

std::vector<Contato> carregar() {
    std::vector<Contato> agenda;
    std::ifstream arquivo(arquivo_agenda);
    if (!arquivo) {
        std::cout << "Nenhuma agenda salva ainda (" << arquivo_agenda << " não existe).\n";
        return agenda;
    }
    std::string linha;
    while (std::getline(arquivo, linha)) {
        std::istringstream campos(linha);
        Contato c;
        std::getline(campos, c.nome, ';');
        std::getline(campos, c.telefone, ';');
        std::getline(campos, c.email);
        if (!c.nome.empty()) {
            agenda.push_back(c);
        }
    }
    std::cout << "Carregados " << agenda.size() << " contatos de " << arquivo_agenda << ".\n";
    return agenda;
}

int main() {
    std::cout << "=== Agenda da Ana ===\n";
    std::vector<Contato> agenda = carregar();

    int opcao = -1;
    while (opcao != 0) {
        std::cout << "\n1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair\n";
        opcao = ler_opcao();
        switch (opcao) {
            case 1: adicionar(agenda); break;
            case 2: listar(agenda); break;
            case 3: buscar(agenda); break;
            case 4: remover(agenda); break;
            case 0: salvar(agenda); break;
            default: std::cout << "Opção inválida.\n";
        }
    }
    std::cout << "Até logo!\n";
}

O projeto juntou struct, vector (push_back, erase, size), switch num while, getline com std::ws, funções de string, referências (const& para ler, & para mudar) e arquivos. O padrão "carregar no começo, salvar no fim" é o de muitos programas de verdade, de editores de texto a jogos.


Desafios extras

  1. Buscar também pelo telefone. Faça a busca encontrar o contato quando o texto aparecer no nome ou no telefone. (Faça no editor, logo abaixo.)
  2. Nada de ; nos campos. No adicionar, se algum campo tiver ;, mostre um aviso e peça de novo (dica: campo.contains(';')).
  3. Lista em ordem alfabética. Depois de adicionar, ordene a agenda pelo nome com std::ranges::sort e uma lambda (veja Algoritmos).
  4. Editar. Uma opção 5 que pergunta o número do contato e deixa trocar o telefone e o e-mail.
  5. Confirmar antes de remover. "Remover Bruno Lima? (s/n)".
  6. Salvar sempre. Hoje, se o programa for fechado com Ctrl+C, as mudanças da sessão se perdem. Chame salvar logo depois de cada adição e remoção.

Desafio 1 no editor: mude a condição da busca para também aceitar quando agenda[i].telefone contiver o texto procurado (use ||, o "ou"). A entrada busca 90000-0002, o telefone do Bruno.

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

struct Contato {
    std::string nome;
    std::string telefone;
    std::string email;
};

const std::string arquivo_agenda = "agenda.txt";

void mostrar(std::size_t numero, const Contato& c) {
    std::cout << numero << ". " << c.nome << " | " << c.telefone << " | " << c.email << '\n';
}

void listar(const std::vector<Contato>& agenda) {
    if (agenda.empty()) {
        std::cout << "A agenda está vazia.\n";
        return;
    }
    std::cout << "--- Contatos (" << agenda.size() << ") ---\n";
    for (std::size_t i = 0; i < agenda.size(); ++i) {
        mostrar(i + 1, agenda[i]);
    }
}

int ler_opcao() {
    int opcao = -1;
    std::cout << "Opção: ";
    if (!(std::cin >> opcao)) {
        if (std::cin.eof()) {
            return 0;  // a entrada acabou: sai (e salva)
        }
        std::cin.clear();
        std::string lixo;
        std::getline(std::cin, lixo);
        return -1;  // não era número: opção inválida
    }
    return opcao;
}

void adicionar(std::vector<Contato>& agenda) {
    Contato novo;
    std::cout << "Nome: ";
    std::getline(std::cin >> std::ws, novo.nome);
    std::cout << "Telefone: ";
    std::getline(std::cin >> std::ws, novo.telefone);
    std::cout << "E-mail: ";
    std::getline(std::cin >> std::ws, novo.email);
    agenda.push_back(novo);
    std::cout << "Contato adicionado: " << novo.nome << '\n';
}

// Devolve uma cópia do texto com as letras A-Z trocadas por a-z.
std::string minusculas(std::string texto) {
    for (char& letra : texto) {
        letra = static_cast<char>(std::tolower(static_cast<unsigned char>(letra)));
    }
    return texto;
}

void buscar(const std::vector<Contato>& agenda) {
    std::string busca;
    std::cout << "Buscar por: ";
    std::getline(std::cin >> std::ws, busca);
    const std::string procurado = minusculas(busca);

    int encontrados = 0;
    for (std::size_t i = 0; i < agenda.size(); ++i) {
        if (minusculas(agenda[i].nome).contains(procurado)) {
            mostrar(i + 1, agenda[i]);
            ++encontrados;
        }
    }
    if (encontrados == 0) {
        std::cout << "Nenhum contato encontrado com \"" << busca << "\".\n";
    }
}

void remover(std::vector<Contato>& agenda) {
    listar(agenda);
    if (agenda.empty()) {
        return;
    }
    std::cout << "Número do contato a remover: ";
    std::size_t numero = 0;
    if (!(std::cin >> numero) || numero < 1 || numero > agenda.size()) {
        std::cin.clear();
        std::string lixo;
        std::getline(std::cin, lixo);
        std::cout << "Número inválido.\n";
        return;
    }
    std::cout << "Removido: " << agenda[numero - 1].nome << '\n';
    agenda.erase(agenda.begin() + (numero - 1));
}

void salvar(const std::vector<Contato>& agenda) {
    std::ofstream arquivo(arquivo_agenda);
    if (!arquivo) {
        std::cout << "Não consegui abrir " << arquivo_agenda << " para gravar.\n";
        return;
    }
    for (const Contato& c : agenda) {
        arquivo << c.nome << ';' << c.telefone << ';' << c.email << '\n';
    }
    std::cout << "Agenda salva em " << arquivo_agenda << " (" << agenda.size() << " contatos).\n";
}

std::vector<Contato> carregar() {
    std::vector<Contato> agenda;
    std::ifstream arquivo(arquivo_agenda);
    if (!arquivo) {
        std::cout << "Nenhuma agenda salva ainda (" << arquivo_agenda << " não existe).\n";
        return agenda;
    }
    std::string linha;
    while (std::getline(arquivo, linha)) {
        std::istringstream campos(linha);
        Contato c;
        std::getline(campos, c.nome, ';');
        std::getline(campos, c.telefone, ';');
        std::getline(campos, c.email);
        if (!c.nome.empty()) {
            agenda.push_back(c);
        }
    }
    std::cout << "Carregados " << agenda.size() << " contatos de " << arquivo_agenda << ".\n";
    return agenda;
}

int main() {
    std::cout << "=== Agenda da Ana ===\n";
    std::vector<Contato> agenda = carregar();

    int opcao = -1;
    while (opcao != 0) {
        std::cout << "\n1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair\n";
        opcao = ler_opcao();
        switch (opcao) {
            case 1: adicionar(agenda); break;
            case 2: listar(agenda); break;
            case 3: buscar(agenda); break;
            case 4: remover(agenda); break;
            case 0: salvar(agenda); break;
            default: std::cout << "Opção inválida.\n";
        }
    }
    std::cout << "Até logo!\n";
}
Ana Souza;(11) 90000-0001;ana@exemplo.com
Bruno Lima;(21) 90000-0002;bruno@exemplo.com
Carla Dias;(31) 90000-0003;carla@exemplo.com
#include <cctype>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

struct Contato {
    std::string nome;
    std::string telefone;
    std::string email;
};

const std::string arquivo_agenda = "agenda.txt";

void mostrar(std::size_t numero, const Contato& c) {
    std::cout << numero << ". " << c.nome << " | " << c.telefone << " | " << c.email << '\n';
}

void listar(const std::vector<Contato>& agenda) {
    if (agenda.empty()) {
        std::cout << "A agenda está vazia.\n";
        return;
    }
    std::cout << "--- Contatos (" << agenda.size() << ") ---\n";
    for (std::size_t i = 0; i < agenda.size(); ++i) {
        mostrar(i + 1, agenda[i]);
    }
}

int ler_opcao() {
    int opcao = -1;
    std::cout << "Opção: ";
    if (!(std::cin >> opcao)) {
        if (std::cin.eof()) {
            return 0;  // a entrada acabou: sai (e salva)
        }
        std::cin.clear();
        std::string lixo;
        std::getline(std::cin, lixo);
        return -1;  // não era número: opção inválida
    }
    return opcao;
}

void adicionar(std::vector<Contato>& agenda) {
    Contato novo;
    std::cout << "Nome: ";
    std::getline(std::cin >> std::ws, novo.nome);
    std::cout << "Telefone: ";
    std::getline(std::cin >> std::ws, novo.telefone);
    std::cout << "E-mail: ";
    std::getline(std::cin >> std::ws, novo.email);
    agenda.push_back(novo);
    std::cout << "Contato adicionado: " << novo.nome << '\n';
}

// Devolve uma cópia do texto com as letras A-Z trocadas por a-z.
std::string minusculas(std::string texto) {
    for (char& letra : texto) {
        letra = static_cast<char>(std::tolower(static_cast<unsigned char>(letra)));
    }
    return texto;
}

void buscar(const std::vector<Contato>& agenda) {
    std::string busca;
    std::cout << "Buscar por: ";
    std::getline(std::cin >> std::ws, busca);
    const std::string procurado = minusculas(busca);

    int encontrados = 0;
    for (std::size_t i = 0; i < agenda.size(); ++i) {
        if (minusculas(agenda[i].nome).contains(procurado) || agenda[i].telefone.contains(procurado)) {
            mostrar(i + 1, agenda[i]);
            ++encontrados;
        }
    }
    if (encontrados == 0) {
        std::cout << "Nenhum contato encontrado com \"" << busca << "\".\n";
    }
}

void remover(std::vector<Contato>& agenda) {
    listar(agenda);
    if (agenda.empty()) {
        return;
    }
    std::cout << "Número do contato a remover: ";
    std::size_t numero = 0;
    if (!(std::cin >> numero) || numero < 1 || numero > agenda.size()) {
        std::cin.clear();
        std::string lixo;
        std::getline(std::cin, lixo);
        std::cout << "Número inválido.\n";
        return;
    }
    std::cout << "Removido: " << agenda[numero - 1].nome << '\n';
    agenda.erase(agenda.begin() + (numero - 1));
}

void salvar(const std::vector<Contato>& agenda) {
    std::ofstream arquivo(arquivo_agenda);
    if (!arquivo) {
        std::cout << "Não consegui abrir " << arquivo_agenda << " para gravar.\n";
        return;
    }
    for (const Contato& c : agenda) {
        arquivo << c.nome << ';' << c.telefone << ';' << c.email << '\n';
    }
    std::cout << "Agenda salva em " << arquivo_agenda << " (" << agenda.size() << " contatos).\n";
}

std::vector<Contato> carregar() {
    std::vector<Contato> agenda;
    std::ifstream arquivo(arquivo_agenda);
    if (!arquivo) {
        std::cout << "Nenhuma agenda salva ainda (" << arquivo_agenda << " não existe).\n";
        return agenda;
    }
    std::string linha;
    while (std::getline(arquivo, linha)) {
        std::istringstream campos(linha);
        Contato c;
        std::getline(campos, c.nome, ';');
        std::getline(campos, c.telefone, ';');
        std::getline(campos, c.email);
        if (!c.nome.empty()) {
            agenda.push_back(c);
        }
    }
    std::cout << "Carregados " << agenda.size() << " contatos de " << arquivo_agenda << ".\n";
    return agenda;
}

int main() {
    std::cout << "=== Agenda da Ana ===\n";
    std::vector<Contato> agenda = carregar();

    int opcao = -1;
    while (opcao != 0) {
        std::cout << "\n1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair\n";
        opcao = ler_opcao();
        switch (opcao) {
            case 1: adicionar(agenda); break;
            case 2: listar(agenda); break;
            case 3: buscar(agenda); break;
            case 4: remover(agenda); break;
            case 0: salvar(agenda); break;
            default: std::cout << "Opção inválida.\n";
        }
    }
    std::cout << "Até logo!\n";
}

Na máquina da Ana, com o arquivo de três contatos:

$ ./agenda
=== Agenda da Ana ===
Carregados 3 contatos de agenda.txt.

1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair
Opção: 3
Buscar por: 90000-0002
2. Bruno Lima | (21) 90000-0002 | bruno@exemplo.com

1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair
Opção: 3
Buscar por: carla
3. Carla Dias | (31) 90000-0003 | carla@exemplo.com

1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair
Opção: 0
Agenda salva em agenda.txt (3 contatos).
Até logo!

Exercícios

1. Depois de std::cin >> opcao;, um std::getline(std::cin, nome); leu um nome vazio sem esperar. Por quê?

2. Por que adicionar recebe std::vector<Contato>& agenda (com & e sem const)?

3. O que acontece com o conteúdo antigo de agenda.txt quando std::ofstream arquivo("agenda.txt"); abre o arquivo?

4. Por que a busca não acha "Érica" quando a Ana digita "érica"?

5. Complete a leitura do telefone, que vai até o próximo ;:

std::getline(campos, c.telefone, );

6. Complete para ler uma linha inteira depois de um cin >>, descartando a quebra de linha que sobrou:

std::getline(std::cin >> , novo.nome);

Resumo

  • Uma struct junta as informações de um contato; um std::vector guarda quantos forem precisos.
  • Menu = while (opcao != 0) + switch + uma função por opção.
  • Para ler uma linha com espaços depois de um >>: std::getline(std::cin >> std::ws, texto).
  • Busca sem diferenciar maiúsculas: compare tudo em minúsculas (std::tolower com unsigned char). Só funciona para letras sem acento.
  • Antes de usar um número digitado como posição, confira se ele existe na lista. erase(begin() + posição) remove.
  • std::ofstream grava (e apaga o conteúdo antigo); std::ifstream lê; std::istringstream + getline(…, ';') separa os campos. Os arquivos fecham sozinhos.
  • O separador (;) não pode aparecer dentro dos dados.

Próximo projeto: Contas Bancárias (POO), em que os dados ganham regras próprias dentro de classes.