Projeto Agenda de Contatos

A Ana quer uma agenda no computador: digitar nome e telefone, listar todo mundo, achar um contato pelo nome (sem se importar com maiúsculas) e apagar quem saiu da vida dela. E, o mais importante, que a agenda não suma quando ela fechar o programa. É o projeto para juntar structs, funções, texto e arquivos num programa de verdade, com um menu que se repete.

Você vai praticar
  • Guardar nome e telefone juntos numa struct e ter um array de contatos.
  • Ler linhas com espaço usando fgets e tirar o \n do fim.
  • Um menu que se repete com while e switch (veja Sobre os Projetos).
  • Buscar sem diferenciar maiúsculas, passando o texto por tolower (ctype.h).
  • Remover um item de um array deslocando os seguintes para trás.
  • Gravar e ler um arquivo de texto com fprintf e sscanf, para a agenda sobreviver ao fechar o programa.

O que vamos construir

A agenda pronta, no terminal da Ana. Ela adiciona dois contatos, lista, busca por "ana", remove a Ana Paula e lista de novo:

$ ./agenda

=== Agenda da Ana ===
1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair
Opção: 1
Nome: Ana Paula
Telefone: 555-1234
Contato adicionado.

=== Agenda da Ana ===
1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair
Opção: 1
Nome: Bruno Costa
Telefone: 555-9000
Contato adicionado.

=== Agenda da Ana ===
1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair
Opção: 2

Nome                 Telefone
Ana Paula            555-1234
Bruno Costa          555-9000

=== Agenda da Ana ===
1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair
Opção: 3
Buscar: ana
Ana Paula            555-1234

=== Agenda da Ana ===
1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair
Opção: 4
Remover (nome exato): Ana Paula
Contato removido.

=== Agenda da Ana ===
1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair
Opção: 2

Nome                 Telefone
Bruno Costa          555-9000

=== Agenda da Ana ===
1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair
Opção: 0
Até logo!
  1. A struct, o array e o menu (adicionar e listar).
  2. Buscar sem diferenciar maiúsculas.
  3. Remover um contato (deslocando o array).
  4. Gravar e carregar o arquivo de texto.

Antes de começar

Você precisa ter visto Structs, Funções, fgets e, para a versão 4, Arquivos. O arquivo vai se chamar agenda.c:

$ mkdir -p c/agenda
$ cd c/agenda
Por que ler tudo com fgets?

Nomes têm espaço ("Ana Paula"), e o scanf("%s") para no primeiro espaço (veja o Boletim de Notas). Por isso a agenda lê cada campo com fgets, que pega a linha inteira. Até a opção do menu é lida com fgets: assim não sobra nenhum \n atrapalhando a próxima leitura — o problema clássico de misturar scanf e fgets.


Versão 1 · A struct, o array e o menu

Primeiro, a base: uma struct Contato com nome e telefone, um array de contatos, e um menu que se repete com duas opções — adicionar e listar. Duas funções ajudam: tirar_novalinha (tira o \n que o fgets deixa) e ler_linha (mostra o rótulo, lê a linha e limpa o \n).

#include <stdio.h>
#include <string.h>

#define MAX_CONTATOS 100
#define TAM_NOME 50
#define TAM_TEL 20

struct Contato {
    char nome[TAM_NOME];
    char telefone[TAM_TEL];
};

void tirar_novalinha(char *s) {
    s[strcspn(s, "\n")] = '\0';
}

int ler_linha(const char *rotulo, char *destino, int tam) {
    printf("%s", rotulo);
    if (fgets(destino, tam, stdin) == NULL) {
        return 0;
    }
    tirar_novalinha(destino);
    return 1;
}

void adicionar(struct Contato agenda[], int *n) {
    struct Contato c = {0};
    ler_linha("Nome: ", c.nome, TAM_NOME);
    ler_linha("Telefone: ", c.telefone, TAM_TEL);
    agenda[*n] = c;
    (*n)++;
    printf("Contato adicionado.\n");
}

void listar(const struct Contato agenda[], int n) {
    if (n == 0) {
        printf("Agenda vazia.\n");
        return;
    }
    printf("\n%-20s %s\n", "Nome", "Telefone");
    for (int i = 0; i < n; i++) {
        printf("%-20s %s\n", agenda[i].nome, agenda[i].telefone);
    }
}

int main(void) {
    struct Contato agenda[MAX_CONTATOS];
    int n = 0;
    char opcao[10];
    int rodando = 1;
    while (rodando) {
        printf("\n=== Agenda da Ana ===\n");
        printf("1. Adicionar  2. Listar  0. Sair\n");
        printf("Opção: ");
        if (fgets(opcao, sizeof opcao, stdin) == NULL) {
            break;
        }
        switch (opcao[0]) {
            case '1': adicionar(agenda, &n); break;
            case '2': listar(agenda, n); break;
            case '0': rodando = 0; break;
            default: printf("Opção inválida.\n");
        }
    }
    printf("Até logo!\n");
    return 0;
}
struct Contato { char nome[TAM_NOME]; char telefone[TAM_TEL]; };
A "ficha" de um contato: nome (até 49 letras + o '\0') e telefone. Os tamanhos têm nome (#define), como manda as boas práticas.
s[strcspn(s, "\n")] = '\0';
O fgets guarda o Enter como \n no fim. O strcspn(s, "\n") dá a posição do \n (ou do fim); pôr um '\0' ali corta o Enter (veja fgets).
int ler_linha(const char *rotulo, char *destino, int tam)
Uma função só para ler um campo com segurança: mostra o rótulo, chama fgets com o tamanho do destino (nunca estoura, veja Segurança) e tira o \n. Devolve 0 se a entrada acabou.
agenda[*n] = c; (*n)++;
Guarda o novo contato na primeira posição livre e aumenta a contagem. O n vem por ponteiro porque a função precisa mudar o n do main.
printf("%-20s %s\n", ...)
%-20s põe o nome numa coluna de 20, à esquerda, para a lista ficar alinhada (veja Formatos do printf).
switch (opcao[0])
A opção é lida como texto; opcao[0] é a primeira tecla ('1', '2', '0'). Comparar o caractere evita o problema do \n do scanf.
$ gcc -std=c23 -Wall -Wextra agenda.c -o agenda
$ ./agenda

=== Agenda da Ana ===
1. Adicionar  2. Listar  0. Sair
Opção: 1
Nome: Ana Paula
Telefone: 555-1234
Contato adicionado.

=== Agenda da Ana ===
1. Adicionar  2. Listar  0. Sair
Opção: 2

Nome                 Telefone
Ana Paula            555-1234

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

Missão (versão 1): a função listar mostra o cabeçalho, mas não os contatos. Escreva o for que percorre agenda[0] até agenda[n - 1] e mostra cada um com printf("%-20s %s\n", agenda[i].nome, agenda[i].telefone);. A entrada adiciona Ana Paula e Bruno Costa e lista.

#include <stdio.h>
#include <string.h>

#define MAX_CONTATOS 100
#define TAM_NOME 50
#define TAM_TEL 20

struct Contato {
    char nome[TAM_NOME];
    char telefone[TAM_TEL];
};

void tirar_novalinha(char *s) {
    s[strcspn(s, "\n")] = '\0';
}

int ler_linha(const char *rotulo, char *destino, int tam) {
    printf("%s", rotulo);
    if (fgets(destino, tam, stdin) == NULL) {
        return 0;
    }
    tirar_novalinha(destino);
    return 1;
}

void adicionar(struct Contato agenda[], int *n) {
    struct Contato c = {0};
    ler_linha("Nome: ", c.nome, TAM_NOME);
    ler_linha("Telefone: ", c.telefone, TAM_TEL);
    agenda[*n] = c;
    (*n)++;
    printf("Contato adicionado.\n");
}

void listar(const struct Contato agenda[], int n) {
    if (n == 0) {
        printf("Agenda vazia.\n");
        return;
    }
    printf("\n%-20s %s\n", "Nome", "Telefone");
    // escreva aqui o for que mostra cada contato (use agenda[i].nome e agenda[i].telefone)
}

int main(void) {
    struct Contato agenda[MAX_CONTATOS];
    int n = 0;
    char opcao[10];
    int rodando = 1;
    while (rodando) {
        printf("\n=== Agenda da Ana ===\n");
        printf("1. Adicionar  2. Listar  0. Sair\n");
        printf("Opção: ");
        if (fgets(opcao, sizeof opcao, stdin) == NULL) {
            break;
        }
        switch (opcao[0]) {
            case '1': adicionar(agenda, &n); break;
            case '2': listar(agenda, n); break;
            case '0': rodando = 0; break;
            default: printf("Opção inválida.\n");
        }
    }
    printf("Até logo!\n");
    return 0;
}
#include <stdio.h>
#include <string.h>

#define MAX_CONTATOS 100
#define TAM_NOME 50
#define TAM_TEL 20

struct Contato {
    char nome[TAM_NOME];
    char telefone[TAM_TEL];
};

void tirar_novalinha(char *s) {
    s[strcspn(s, "\n")] = '\0';
}

int ler_linha(const char *rotulo, char *destino, int tam) {
    printf("%s", rotulo);
    if (fgets(destino, tam, stdin) == NULL) {
        return 0;
    }
    tirar_novalinha(destino);
    return 1;
}

void adicionar(struct Contato agenda[], int *n) {
    struct Contato c = {0};
    ler_linha("Nome: ", c.nome, TAM_NOME);
    ler_linha("Telefone: ", c.telefone, TAM_TEL);
    agenda[*n] = c;
    (*n)++;
    printf("Contato adicionado.\n");
}

void listar(const struct Contato agenda[], int n) {
    if (n == 0) {
        printf("Agenda vazia.\n");
        return;
    }
    printf("\n%-20s %s\n", "Nome", "Telefone");
    for (int i = 0; i < n; i++) {
        printf("%-20s %s\n", agenda[i].nome, agenda[i].telefone);
    }
}

int main(void) {
    struct Contato agenda[MAX_CONTATOS];
    int n = 0;
    char opcao[10];
    int rodando = 1;
    while (rodando) {
        printf("\n=== Agenda da Ana ===\n");
        printf("1. Adicionar  2. Listar  0. Sair\n");
        printf("Opção: ");
        if (fgets(opcao, sizeof opcao, stdin) == NULL) {
            break;
        }
        switch (opcao[0]) {
            case '1': adicionar(agenda, &n); break;
            case '2': listar(agenda, n); break;
            case '0': rodando = 0; break;
            default: printf("Opção inválida.\n");
        }
    }
    printf("Até logo!\n");
    return 0;
}

Versão 2 · Buscar sem diferenciar maiúsculas

A Ana digita "ana" e quer achar "Ana Paula". Para isso, a busca não pode diferenciar maiúsculas de minúsculas. A ideia: passar os dois textos para minúsculas (com tolower) e ver se um está dentro do outro com strstr (veja Funções de String). A função contem_ignorando_caso faz isso; a buscar a usa em cada contato.

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define MAX_CONTATOS 100
#define TAM_NOME 50
#define TAM_TEL 20

struct Contato {
    char nome[TAM_NOME];
    char telefone[TAM_TEL];
};

void tirar_novalinha(char *s) {
    s[strcspn(s, "\n")] = '\0';
}

int ler_linha(const char *rotulo, char *destino, int tam) {
    printf("%s", rotulo);
    if (fgets(destino, tam, stdin) == NULL) {
        return 0;
    }
    tirar_novalinha(destino);
    return 1;
}

int contem_ignorando_caso(const char *texto, const char *parte) {
    char t[TAM_NOME];
    char p[TAM_NOME];
    int i;
    for (i = 0; texto[i] != '\0' && i < TAM_NOME - 1; i++) {
        t[i] = (char) tolower((unsigned char) texto[i]);
    }
    t[i] = '\0';
    for (i = 0; parte[i] != '\0' && i < TAM_NOME - 1; i++) {
        p[i] = (char) tolower((unsigned char) parte[i]);
    }
    p[i] = '\0';
    return strstr(t, p) != NULL;
}

void adicionar(struct Contato agenda[], int *n) {
    struct Contato c = {0};
    ler_linha("Nome: ", c.nome, TAM_NOME);
    ler_linha("Telefone: ", c.telefone, TAM_TEL);
    agenda[*n] = c;
    (*n)++;
    printf("Contato adicionado.\n");
}

void buscar(const struct Contato agenda[], int n) {
    char alvo[TAM_NOME];
    ler_linha("Buscar: ", alvo, TAM_NOME);
    int achou = 0;
    for (int i = 0; i < n; i++) {
        if (contem_ignorando_caso(agenda[i].nome, alvo)) {
            printf("%-20s %s\n", agenda[i].nome, agenda[i].telefone);
            achou = 1;
        }
    }
    if (!achou) {
        printf("Nenhum contato com \"%s\".\n", alvo);
    }
}

int main(void) {
    struct Contato agenda[MAX_CONTATOS];
    int n = 0;
    char opcao[10];
    int rodando = 1;
    while (rodando) {
        printf("\n=== Agenda da Ana ===\n");
        printf("1. Adicionar  3. Buscar  0. Sair\n");
        printf("Opção: ");
        if (fgets(opcao, sizeof opcao, stdin) == NULL) {
            break;
        }
        switch (opcao[0]) {
            case '1': adicionar(agenda, &n); break;
            case '3': buscar(agenda, n); break;
            case '0': rodando = 0; break;
            default: printf("Opção inválida.\n");
        }
    }
    printf("Até logo!\n");
    return 0;
}
tolower((unsigned char) texto[i])
Transforma a letra em minúscula. O (unsigned char) evita um problema com acentos e valores negativos (veja ctype.h).
strstr(t, p) != NULL
strstr ("string in string") procura o texto p dentro de t e devolve onde ele começa, ou NULL se não achar. Assim a busca acha parte do nome: "cost" acha "Bruno Costa".
if (contem_ignorando_caso(agenda[i].nome, alvo))
Para cada contato, pergunta "o nome contém o que a Ana procura?". Se sim, mostra e marca achou = 1.
$ ./agenda

=== Agenda da Ana ===
1. Adicionar  3. Buscar  0. Sair
Opção: 3
Buscar: ANA
Ana Paula            555-1234

=== Agenda da Ana ===
1. Adicionar  3. Buscar  0. Sair
Opção: 3
Buscar: zeca
Nenhum contato com "zeca".

"ANA" em maiúsculas achou "Ana Paula": a busca não liga para o tamanho das letras. E um nome que não existe dá uma mensagem clara, em vez de silêncio.

Missão (versão 2): a função buscar tem o laço, mas não compara. Dentro do for, se contem_ignorando_caso(agenda[i].nome, alvo) for verdadeiro, mostre o contato com printf("%-20s %s\n", agenda[i].nome, agenda[i].telefone); e faça achou = 1;. A entrada busca "ana" e deve achar a Ana Paula.

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define MAX_CONTATOS 100
#define TAM_NOME 50
#define TAM_TEL 20

struct Contato {
    char nome[TAM_NOME];
    char telefone[TAM_TEL];
};

void tirar_novalinha(char *s) {
    s[strcspn(s, "\n")] = '\0';
}

int ler_linha(const char *rotulo, char *destino, int tam) {
    printf("%s", rotulo);
    if (fgets(destino, tam, stdin) == NULL) {
        return 0;
    }
    tirar_novalinha(destino);
    return 1;
}

int contem_ignorando_caso(const char *texto, const char *parte) {
    char t[TAM_NOME];
    char p[TAM_NOME];
    int i;
    for (i = 0; texto[i] != '\0' && i < TAM_NOME - 1; i++) {
        t[i] = (char) tolower((unsigned char) texto[i]);
    }
    t[i] = '\0';
    for (i = 0; parte[i] != '\0' && i < TAM_NOME - 1; i++) {
        p[i] = (char) tolower((unsigned char) parte[i]);
    }
    p[i] = '\0';
    return strstr(t, p) != NULL;
}

void adicionar(struct Contato agenda[], int *n) {
    struct Contato c = {0};
    ler_linha("Nome: ", c.nome, TAM_NOME);
    ler_linha("Telefone: ", c.telefone, TAM_TEL);
    agenda[*n] = c;
    (*n)++;
    printf("Contato adicionado.\n");
}

void buscar(const struct Contato agenda[], int n) {
    char alvo[TAM_NOME];
    ler_linha("Buscar: ", alvo, TAM_NOME);
    int achou = 0;
    for (int i = 0; i < n; i++) {
        // se o nome do contato i contém "alvo" (sem diferenciar maiúsculas),
        // mostre-o e faça achou = 1
    }
    if (!achou) {
        printf("Nenhum contato com \"%s\".\n", alvo);
    }
}

int main(void) {
    struct Contato agenda[MAX_CONTATOS];
    int n = 0;
    char opcao[10];
    int rodando = 1;
    while (rodando) {
        printf("\n=== Agenda da Ana ===\n");
        printf("1. Adicionar  3. Buscar  0. Sair\n");
        printf("Opção: ");
        if (fgets(opcao, sizeof opcao, stdin) == NULL) {
            break;
        }
        switch (opcao[0]) {
            case '1': adicionar(agenda, &n); break;
            case '3': buscar(agenda, n); break;
            case '0': rodando = 0; break;
            default: printf("Opção inválida.\n");
        }
    }
    printf("Até logo!\n");
    return 0;
}
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define MAX_CONTATOS 100
#define TAM_NOME 50
#define TAM_TEL 20

struct Contato {
    char nome[TAM_NOME];
    char telefone[TAM_TEL];
};

void tirar_novalinha(char *s) {
    s[strcspn(s, "\n")] = '\0';
}

int ler_linha(const char *rotulo, char *destino, int tam) {
    printf("%s", rotulo);
    if (fgets(destino, tam, stdin) == NULL) {
        return 0;
    }
    tirar_novalinha(destino);
    return 1;
}

int contem_ignorando_caso(const char *texto, const char *parte) {
    char t[TAM_NOME];
    char p[TAM_NOME];
    int i;
    for (i = 0; texto[i] != '\0' && i < TAM_NOME - 1; i++) {
        t[i] = (char) tolower((unsigned char) texto[i]);
    }
    t[i] = '\0';
    for (i = 0; parte[i] != '\0' && i < TAM_NOME - 1; i++) {
        p[i] = (char) tolower((unsigned char) parte[i]);
    }
    p[i] = '\0';
    return strstr(t, p) != NULL;
}

void adicionar(struct Contato agenda[], int *n) {
    struct Contato c = {0};
    ler_linha("Nome: ", c.nome, TAM_NOME);
    ler_linha("Telefone: ", c.telefone, TAM_TEL);
    agenda[*n] = c;
    (*n)++;
    printf("Contato adicionado.\n");
}

void buscar(const struct Contato agenda[], int n) {
    char alvo[TAM_NOME];
    ler_linha("Buscar: ", alvo, TAM_NOME);
    int achou = 0;
    for (int i = 0; i < n; i++) {
        if (contem_ignorando_caso(agenda[i].nome, alvo)) {
            printf("%-20s %s\n", agenda[i].nome, agenda[i].telefone);
            achou = 1;
        }
    }
    if (!achou) {
        printf("Nenhum contato com \"%s\".\n", alvo);
    }
}

int main(void) {
    struct Contato agenda[MAX_CONTATOS];
    int n = 0;
    char opcao[10];
    int rodando = 1;
    while (rodando) {
        printf("\n=== Agenda da Ana ===\n");
        printf("1. Adicionar  3. Buscar  0. Sair\n");
        printf("Opção: ");
        if (fgets(opcao, sizeof opcao, stdin) == NULL) {
            break;
        }
        switch (opcao[0]) {
            case '1': adicionar(agenda, &n); break;
            case '3': buscar(agenda, n); break;
            case '0': rodando = 0; break;
            default: printf("Opção inválida.\n");
        }
    }
    printf("Até logo!\n");
    return 0;
}

Versão 3 · Remover um contato

Remover de um array é mais sutil do que parece: não basta "apagar" a posição, porque ficaria um buraco no meio. O jeito certo é deslocar todos os contatos seguintes uma posição para trás, tampando o buraco, e depois diminuir a contagem.

Antes (remover o índice 0):   [Ana Paula][Bruno Costa][Diego]   n = 3
Desloca para trás:            [Bruno Costa][Diego][Diego]      « cada um copia o seguinte
Diminui a contagem:           [Bruno Costa][Diego]             n = 2
#include <stdio.h>
#include <string.h>

#define MAX_CONTATOS 100
#define TAM_NOME 50
#define TAM_TEL 20

struct Contato {
    char nome[TAM_NOME];
    char telefone[TAM_TEL];
};

void tirar_novalinha(char *s) {
    s[strcspn(s, "\n")] = '\0';
}

int ler_linha(const char *rotulo, char *destino, int tam) {
    printf("%s", rotulo);
    if (fgets(destino, tam, stdin) == NULL) {
        return 0;
    }
    tirar_novalinha(destino);
    return 1;
}

void adicionar(struct Contato agenda[], int *n) {
    struct Contato c = {0};
    ler_linha("Nome: ", c.nome, TAM_NOME);
    ler_linha("Telefone: ", c.telefone, TAM_TEL);
    agenda[*n] = c;
    (*n)++;
    printf("Contato adicionado.\n");
}

void listar(const struct Contato agenda[], int n) {
    if (n == 0) {
        printf("Agenda vazia.\n");
        return;
    }
    printf("\n%-20s %s\n", "Nome", "Telefone");
    for (int i = 0; i < n; i++) {
        printf("%-20s %s\n", agenda[i].nome, agenda[i].telefone);
    }
}

void remover(struct Contato agenda[], int *n) {
    char alvo[TAM_NOME];
    ler_linha("Remover (nome exato): ", alvo, TAM_NOME);
    for (int i = 0; i < *n; i++) {
        if (strcmp(agenda[i].nome, alvo) == 0) {
            for (int j = i; j < *n - 1; j++) {
                agenda[j] = agenda[j + 1];
            }
            (*n)--;
            printf("Contato removido.\n");
            return;
        }
    }
    printf("Contato \"%s\" não encontrado.\n", alvo);
}

int main(void) {
    struct Contato agenda[MAX_CONTATOS];
    int n = 0;
    char opcao[10];
    int rodando = 1;
    while (rodando) {
        printf("\n=== Agenda da Ana ===\n");
        printf("1. Adicionar  2. Listar  4. Remover  0. Sair\n");
        printf("Opção: ");
        if (fgets(opcao, sizeof opcao, stdin) == NULL) {
            break;
        }
        switch (opcao[0]) {
            case '1': adicionar(agenda, &n); break;
            case '2': listar(agenda, n); break;
            case '4': remover(agenda, &n); break;
            case '0': rodando = 0; break;
            default: printf("Opção inválida.\n");
        }
    }
    printf("Até logo!\n");
    return 0;
}
if (strcmp(agenda[i].nome, alvo) == 0)
Acha o contato com o nome exato. O strcmp devolve 0 quando os textos são iguais (veja Funções de String).
for (int j = i; j < *n - 1; j++) agenda[j] = agenda[j + 1];
O coração da remoção: cada contato, do achado em diante, recebe o seguinte. Uma struct inteira é copiada de uma vez com =.
(*n)--;
Agora há um contato a menos. A última posição tem uma cópia velha, mas ela está fora da contagem e nunca é mostrada.
return;
Para no primeiro que casa: remove um contato por vez.
$ ./agenda

=== Agenda da Ana ===
1. Adicionar  2. Listar  4. Remover  0. Sair
Opção: 4
Remover (nome exato): Ana Paula
Contato removido.

=== Agenda da Ana ===
1. Adicionar  2. Listar  4. Remover  0. Sair
Opção: 2

Nome                 Telefone
Bruno Costa          555-9000

Missão (versão 3): a remover acha o contato e diminui a contagem, mas esqueceu de deslocar os seguintes — então o removido continua na lista. Escreva o for interno que, de j = i até *n - 2, faz agenda[j] = agenda[j + 1];. A entrada adiciona Ana Paula e Bruno Costa, remove a Ana Paula e lista: só deve sobrar o Bruno Costa.

#include <stdio.h>
#include <string.h>

#define MAX_CONTATOS 100
#define TAM_NOME 50
#define TAM_TEL 20

struct Contato {
    char nome[TAM_NOME];
    char telefone[TAM_TEL];
};

void tirar_novalinha(char *s) {
    s[strcspn(s, "\n")] = '\0';
}

int ler_linha(const char *rotulo, char *destino, int tam) {
    printf("%s", rotulo);
    if (fgets(destino, tam, stdin) == NULL) {
        return 0;
    }
    tirar_novalinha(destino);
    return 1;
}

void adicionar(struct Contato agenda[], int *n) {
    struct Contato c = {0};
    ler_linha("Nome: ", c.nome, TAM_NOME);
    ler_linha("Telefone: ", c.telefone, TAM_TEL);
    agenda[*n] = c;
    (*n)++;
    printf("Contato adicionado.\n");
}

void listar(const struct Contato agenda[], int n) {
    if (n == 0) {
        printf("Agenda vazia.\n");
        return;
    }
    printf("\n%-20s %s\n", "Nome", "Telefone");
    for (int i = 0; i < n; i++) {
        printf("%-20s %s\n", agenda[i].nome, agenda[i].telefone);
    }
}

void remover(struct Contato agenda[], int *n) {
    char alvo[TAM_NOME];
    ler_linha("Remover (nome exato): ", alvo, TAM_NOME);
    for (int i = 0; i < *n; i++) {
        if (strcmp(agenda[i].nome, alvo) == 0) {
            // desloque cada contato seguinte uma posição para trás,
            // de j = i até *n - 2:  agenda[j] = agenda[j + 1];
            (*n)--;
            printf("Contato removido.\n");
            return;
        }
    }
    printf("Contato \"%s\" não encontrado.\n", alvo);
}

int main(void) {
    struct Contato agenda[MAX_CONTATOS];
    int n = 0;
    char opcao[10];
    int rodando = 1;
    while (rodando) {
        printf("\n=== Agenda da Ana ===\n");
        printf("1. Adicionar  2. Listar  4. Remover  0. Sair\n");
        printf("Opção: ");
        if (fgets(opcao, sizeof opcao, stdin) == NULL) {
            break;
        }
        switch (opcao[0]) {
            case '1': adicionar(agenda, &n); break;
            case '2': listar(agenda, n); break;
            case '4': remover(agenda, &n); break;
            case '0': rodando = 0; break;
            default: printf("Opção inválida.\n");
        }
    }
    printf("Até logo!\n");
    return 0;
}
#include <stdio.h>
#include <string.h>

#define MAX_CONTATOS 100
#define TAM_NOME 50
#define TAM_TEL 20

struct Contato {
    char nome[TAM_NOME];
    char telefone[TAM_TEL];
};

void tirar_novalinha(char *s) {
    s[strcspn(s, "\n")] = '\0';
}

int ler_linha(const char *rotulo, char *destino, int tam) {
    printf("%s", rotulo);
    if (fgets(destino, tam, stdin) == NULL) {
        return 0;
    }
    tirar_novalinha(destino);
    return 1;
}

void adicionar(struct Contato agenda[], int *n) {
    struct Contato c = {0};
    ler_linha("Nome: ", c.nome, TAM_NOME);
    ler_linha("Telefone: ", c.telefone, TAM_TEL);
    agenda[*n] = c;
    (*n)++;
    printf("Contato adicionado.\n");
}

void listar(const struct Contato agenda[], int n) {
    if (n == 0) {
        printf("Agenda vazia.\n");
        return;
    }
    printf("\n%-20s %s\n", "Nome", "Telefone");
    for (int i = 0; i < n; i++) {
        printf("%-20s %s\n", agenda[i].nome, agenda[i].telefone);
    }
}

void remover(struct Contato agenda[], int *n) {
    char alvo[TAM_NOME];
    ler_linha("Remover (nome exato): ", alvo, TAM_NOME);
    for (int i = 0; i < *n; i++) {
        if (strcmp(agenda[i].nome, alvo) == 0) {
            for (int j = i; j < *n - 1; j++) {
                agenda[j] = agenda[j + 1];
            }
            (*n)--;
            printf("Contato removido.\n");
            return;
        }
    }
    printf("Contato \"%s\" não encontrado.\n", alvo);
}

int main(void) {
    struct Contato agenda[MAX_CONTATOS];
    int n = 0;
    char opcao[10];
    int rodando = 1;
    while (rodando) {
        printf("\n=== Agenda da Ana ===\n");
        printf("1. Adicionar  2. Listar  4. Remover  0. Sair\n");
        printf("Opção: ");
        if (fgets(opcao, sizeof opcao, stdin) == NULL) {
            break;
        }
        switch (opcao[0]) {
            case '1': adicionar(agenda, &n); break;
            case '2': listar(agenda, n); break;
            case '4': remover(agenda, &n); break;
            case '0': rodando = 0; break;
            default: printf("Opção inválida.\n");
        }
    }
    printf("Até logo!\n");
    return 0;
}

Versão 4 · Gravar e carregar o arquivo

Até aqui a agenda vivia só na memória: ao fechar o programa, tudo sumia. A versão final grava os contatos num arquivo de texto, agenda.txt, e o lê de volta quando abre. Duas funções novas:

  • salvar abre o arquivo para escrita ("w") e grava cada contato numa linha nome;telefone com fprintf (veja Escrever Arquivos).
  • carregar abre o arquivo para leitura ("r"), lê cada linha com fgets e separa o nome do telefone com sscanf (veja Ler Arquivos).

O main chama carregar logo no começo e salvar depois de toda mudança (adicionar e remover). O menu ganha as quatro opções.

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define MAX_CONTATOS 100
#define TAM_NOME 50
#define TAM_TEL 20
#define ARQUIVO "agenda.txt"

struct Contato {
    char nome[TAM_NOME];
    char telefone[TAM_TEL];
};

void tirar_novalinha(char *s) {
    s[strcspn(s, "\n")] = '\0';
}

int ler_linha(const char *rotulo, char *destino, int tam) {
    printf("%s", rotulo);
    if (fgets(destino, tam, stdin) == NULL) {
        return 0;
    }
    tirar_novalinha(destino);
    return 1;
}

int contem_ignorando_caso(const char *texto, const char *parte) {
    char t[TAM_NOME];
    char p[TAM_NOME];
    int i;
    for (i = 0; texto[i] != '\0' && i < TAM_NOME - 1; i++) {
        t[i] = (char) tolower((unsigned char) texto[i]);
    }
    t[i] = '\0';
    for (i = 0; parte[i] != '\0' && i < TAM_NOME - 1; i++) {
        p[i] = (char) tolower((unsigned char) parte[i]);
    }
    p[i] = '\0';
    return strstr(t, p) != NULL;
}

void adicionar(struct Contato agenda[], int *n) {
    if (*n >= MAX_CONTATOS) {
        printf("Agenda cheia.\n");
        return;
    }
    struct Contato c = {0};
    ler_linha("Nome: ", c.nome, TAM_NOME);
    ler_linha("Telefone: ", c.telefone, TAM_TEL);
    agenda[*n] = c;
    (*n)++;
    printf("Contato adicionado.\n");
}

void listar(const struct Contato agenda[], int n) {
    if (n == 0) {
        printf("Agenda vazia.\n");
        return;
    }
    printf("\n%-20s %s\n", "Nome", "Telefone");
    for (int i = 0; i < n; i++) {
        printf("%-20s %s\n", agenda[i].nome, agenda[i].telefone);
    }
}

void buscar(const struct Contato agenda[], int n) {
    char alvo[TAM_NOME];
    ler_linha("Buscar: ", alvo, TAM_NOME);
    int achou = 0;
    for (int i = 0; i < n; i++) {
        if (contem_ignorando_caso(agenda[i].nome, alvo)) {
            printf("%-20s %s\n", agenda[i].nome, agenda[i].telefone);
            achou = 1;
        }
    }
    if (!achou) {
        printf("Nenhum contato com \"%s\".\n", alvo);
    }
}

void remover(struct Contato agenda[], int *n) {
    char alvo[TAM_NOME];
    ler_linha("Remover (nome exato): ", alvo, TAM_NOME);
    for (int i = 0; i < *n; i++) {
        if (strcmp(agenda[i].nome, alvo) == 0) {
            for (int j = i; j < *n - 1; j++) {
                agenda[j] = agenda[j + 1];
            }
            (*n)--;
            printf("Contato removido.\n");
            return;
        }
    }
    printf("Contato \"%s\" não encontrado.\n", alvo);
}

void salvar(const struct Contato agenda[], int n) {
    FILE *f = fopen(ARQUIVO, "w");
    if (f == NULL) {
        perror(ARQUIVO);
        return;
    }
    for (int i = 0; i < n; i++) {
        fprintf(f, "%s;%s\n", agenda[i].nome, agenda[i].telefone);
    }
    fclose(f);
}

void carregar(struct Contato agenda[], int *n) {
    FILE *f = fopen(ARQUIVO, "r");
    if (f == NULL) {
        return;
    }
    char linha[TAM_NOME + TAM_TEL];
    while (*n < MAX_CONTATOS && fgets(linha, sizeof linha, f) != NULL) {
        if (sscanf(linha, "%49[^;];%19[^\n]", agenda[*n].nome, agenda[*n].telefone) == 2) {
            (*n)++;
        }
    }
    fclose(f);
}

int main(void) {
    struct Contato agenda[MAX_CONTATOS];
    int n = 0;
    carregar(agenda, &n);

    char opcao[10];
    int rodando = 1;
    while (rodando) {
        printf("\n=== Agenda da Ana ===\n");
        printf("1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair\n");
        printf("Opção: ");
        if (fgets(opcao, sizeof opcao, stdin) == NULL) {
            break;
        }
        switch (opcao[0]) {
            case '1': adicionar(agenda, &n); salvar(agenda, n); break;
            case '2': listar(agenda, n); break;
            case '3': buscar(agenda, n); break;
            case '4': remover(agenda, &n); salvar(agenda, n); break;
            case '0': rodando = 0; break;
            default: printf("Opção inválida.\n");
        }
    }
    printf("Até logo!\n");
    return 0;
}
FILE *f = fopen(ARQUIVO, "w");
Abre agenda.txt para escrita; o "w" cria o arquivo (ou apaga o conteúdo antigo). Se fopen falhar, devolve NULL — por isso o if logo depois, com perror.
fprintf(f, "%s;%s\n", ...)
Grava nome;telefone e um Enter. O ; separa os dois campos; por isso o telefone não pode ter ;.
fopen(ARQUIVO, "r")
Abre para leitura. Se o arquivo ainda não existe (primeira vez), devolve NULL e carregar simplesmente volta: a agenda começa vazia.
sscanf(linha, "%49[^;];%19[^\n]", ...)
O %49[^;] lê até 49 caracteres que não sejam ; (o nome, com espaços e tudo); o ; casa o separador; o %19[^\n] lê o resto da linha (o telefone). As larguras 49 e 19 protegem os arrays. Se vierem os dois campos (== 2), o contato entra.
salvar(agenda, n); depois de adicionar e remover
A agenda é gravada a cada mudança, então nunca se perde nada, mesmo que o programa feche sem querer.

Agora a prova dos nove: adicionar, fechar o programa e abrir de novo. O contato continua lá, porque veio do arquivo:

$ ls agenda.txt
ls: não foi possível acessar 'agenda.txt': Arquivo ou diretório inexistente
$ ./agenda
«… a Ana adiciona a Ana Paula e escolhe 0 para sair …»
Até logo!
$ cat agenda.txt
Ana Paula;555-1234
$ ./agenda                 « abre de novo, sem readicionar ninguém
=== Agenda da Ana ===
1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair
Opção: 2

Nome                 Telefone
Ana Paula            555-1234

O arquivo agenda.txt é texto puro: você pode abri-lo em qualquer editor e ver os contatos. Essa é a graça de um formato de texto — simples de gravar, de ler e de conferir.

Missão final (versão 4): o agenda.txt já tem dois contatos (está na aba ao lado), mas a função carregar não os lê — ao listar, aparece "Agenda vazia". Complete o laço de carregar: para cada linha, use sscanf(linha, "%49[^;];%19[^\n]", agenda[*n].nome, agenda[*n].telefone) e, se ele devolver 2, faça (*n)++. A entrada só lista (opção 2) e sai; devem aparecer os dois contatos do arquivo.

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define MAX_CONTATOS 100
#define TAM_NOME 50
#define TAM_TEL 20
#define ARQUIVO "agenda.txt"

struct Contato {
    char nome[TAM_NOME];
    char telefone[TAM_TEL];
};

void tirar_novalinha(char *s) {
    s[strcspn(s, "\n")] = '\0';
}

int ler_linha(const char *rotulo, char *destino, int tam) {
    printf("%s", rotulo);
    if (fgets(destino, tam, stdin) == NULL) {
        return 0;
    }
    tirar_novalinha(destino);
    return 1;
}

int contem_ignorando_caso(const char *texto, const char *parte) {
    char t[TAM_NOME];
    char p[TAM_NOME];
    int i;
    for (i = 0; texto[i] != '\0' && i < TAM_NOME - 1; i++) {
        t[i] = (char) tolower((unsigned char) texto[i]);
    }
    t[i] = '\0';
    for (i = 0; parte[i] != '\0' && i < TAM_NOME - 1; i++) {
        p[i] = (char) tolower((unsigned char) parte[i]);
    }
    p[i] = '\0';
    return strstr(t, p) != NULL;
}

void adicionar(struct Contato agenda[], int *n) {
    if (*n >= MAX_CONTATOS) {
        printf("Agenda cheia.\n");
        return;
    }
    struct Contato c = {0};
    ler_linha("Nome: ", c.nome, TAM_NOME);
    ler_linha("Telefone: ", c.telefone, TAM_TEL);
    agenda[*n] = c;
    (*n)++;
    printf("Contato adicionado.\n");
}

void listar(const struct Contato agenda[], int n) {
    if (n == 0) {
        printf("Agenda vazia.\n");
        return;
    }
    printf("\n%-20s %s\n", "Nome", "Telefone");
    for (int i = 0; i < n; i++) {
        printf("%-20s %s\n", agenda[i].nome, agenda[i].telefone);
    }
}

void buscar(const struct Contato agenda[], int n) {
    char alvo[TAM_NOME];
    ler_linha("Buscar: ", alvo, TAM_NOME);
    int achou = 0;
    for (int i = 0; i < n; i++) {
        if (contem_ignorando_caso(agenda[i].nome, alvo)) {
            printf("%-20s %s\n", agenda[i].nome, agenda[i].telefone);
            achou = 1;
        }
    }
    if (!achou) {
        printf("Nenhum contato com \"%s\".\n", alvo);
    }
}

void remover(struct Contato agenda[], int *n) {
    char alvo[TAM_NOME];
    ler_linha("Remover (nome exato): ", alvo, TAM_NOME);
    for (int i = 0; i < *n; i++) {
        if (strcmp(agenda[i].nome, alvo) == 0) {
            for (int j = i; j < *n - 1; j++) {
                agenda[j] = agenda[j + 1];
            }
            (*n)--;
            printf("Contato removido.\n");
            return;
        }
    }
    printf("Contato \"%s\" não encontrado.\n", alvo);
}

void salvar(const struct Contato agenda[], int n) {
    FILE *f = fopen(ARQUIVO, "w");
    if (f == NULL) {
        perror(ARQUIVO);
        return;
    }
    for (int i = 0; i < n; i++) {
        fprintf(f, "%s;%s\n", agenda[i].nome, agenda[i].telefone);
    }
    fclose(f);
}

void carregar(struct Contato agenda[], int *n) {
    FILE *f = fopen(ARQUIVO, "r");
    if (f == NULL) {
        return;
    }
    char linha[TAM_NOME + TAM_TEL];
    while (*n < MAX_CONTATOS && fgets(linha, sizeof linha, f) != NULL) {
        // separe a linha "nome;telefone" com sscanf e, se vierem os 2 campos,
        // guarde em agenda[*n] e faça (*n)++
    }
    fclose(f);
}

int main(void) {
    struct Contato agenda[MAX_CONTATOS];
    int n = 0;
    carregar(agenda, &n);

    char opcao[10];
    int rodando = 1;
    while (rodando) {
        printf("\n=== Agenda da Ana ===\n");
        printf("1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair\n");
        printf("Opção: ");
        if (fgets(opcao, sizeof opcao, stdin) == NULL) {
            break;
        }
        switch (opcao[0]) {
            case '1': adicionar(agenda, &n); salvar(agenda, n); break;
            case '2': listar(agenda, n); break;
            case '3': buscar(agenda, n); break;
            case '4': remover(agenda, &n); salvar(agenda, n); break;
            case '0': rodando = 0; break;
            default: printf("Opção inválida.\n");
        }
    }
    printf("Até logo!\n");
    return 0;
}
Ana Paula;555-1234
Bruno Costa;555-9000
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define MAX_CONTATOS 100
#define TAM_NOME 50
#define TAM_TEL 20
#define ARQUIVO "agenda.txt"

struct Contato {
    char nome[TAM_NOME];
    char telefone[TAM_TEL];
};

void tirar_novalinha(char *s) {
    s[strcspn(s, "\n")] = '\0';
}

int ler_linha(const char *rotulo, char *destino, int tam) {
    printf("%s", rotulo);
    if (fgets(destino, tam, stdin) == NULL) {
        return 0;
    }
    tirar_novalinha(destino);
    return 1;
}

int contem_ignorando_caso(const char *texto, const char *parte) {
    char t[TAM_NOME];
    char p[TAM_NOME];
    int i;
    for (i = 0; texto[i] != '\0' && i < TAM_NOME - 1; i++) {
        t[i] = (char) tolower((unsigned char) texto[i]);
    }
    t[i] = '\0';
    for (i = 0; parte[i] != '\0' && i < TAM_NOME - 1; i++) {
        p[i] = (char) tolower((unsigned char) parte[i]);
    }
    p[i] = '\0';
    return strstr(t, p) != NULL;
}

void adicionar(struct Contato agenda[], int *n) {
    if (*n >= MAX_CONTATOS) {
        printf("Agenda cheia.\n");
        return;
    }
    struct Contato c = {0};
    ler_linha("Nome: ", c.nome, TAM_NOME);
    ler_linha("Telefone: ", c.telefone, TAM_TEL);
    agenda[*n] = c;
    (*n)++;
    printf("Contato adicionado.\n");
}

void listar(const struct Contato agenda[], int n) {
    if (n == 0) {
        printf("Agenda vazia.\n");
        return;
    }
    printf("\n%-20s %s\n", "Nome", "Telefone");
    for (int i = 0; i < n; i++) {
        printf("%-20s %s\n", agenda[i].nome, agenda[i].telefone);
    }
}

void buscar(const struct Contato agenda[], int n) {
    char alvo[TAM_NOME];
    ler_linha("Buscar: ", alvo, TAM_NOME);
    int achou = 0;
    for (int i = 0; i < n; i++) {
        if (contem_ignorando_caso(agenda[i].nome, alvo)) {
            printf("%-20s %s\n", agenda[i].nome, agenda[i].telefone);
            achou = 1;
        }
    }
    if (!achou) {
        printf("Nenhum contato com \"%s\".\n", alvo);
    }
}

void remover(struct Contato agenda[], int *n) {
    char alvo[TAM_NOME];
    ler_linha("Remover (nome exato): ", alvo, TAM_NOME);
    for (int i = 0; i < *n; i++) {
        if (strcmp(agenda[i].nome, alvo) == 0) {
            for (int j = i; j < *n - 1; j++) {
                agenda[j] = agenda[j + 1];
            }
            (*n)--;
            printf("Contato removido.\n");
            return;
        }
    }
    printf("Contato \"%s\" não encontrado.\n", alvo);
}

void salvar(const struct Contato agenda[], int n) {
    FILE *f = fopen(ARQUIVO, "w");
    if (f == NULL) {
        perror(ARQUIVO);
        return;
    }
    for (int i = 0; i < n; i++) {
        fprintf(f, "%s;%s\n", agenda[i].nome, agenda[i].telefone);
    }
    fclose(f);
}

void carregar(struct Contato agenda[], int *n) {
    FILE *f = fopen(ARQUIVO, "r");
    if (f == NULL) {
        return;
    }
    char linha[TAM_NOME + TAM_TEL];
    while (*n < MAX_CONTATOS && fgets(linha, sizeof linha, f) != NULL) {
        if (sscanf(linha, "%49[^;];%19[^\n]", agenda[*n].nome, agenda[*n].telefone) == 2) {
            (*n)++;
        }
    }
    fclose(f);
}

int main(void) {
    struct Contato agenda[MAX_CONTATOS];
    int n = 0;
    carregar(agenda, &n);

    char opcao[10];
    int rodando = 1;
    while (rodando) {
        printf("\n=== Agenda da Ana ===\n");
        printf("1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair\n");
        printf("Opção: ");
        if (fgets(opcao, sizeof opcao, stdin) == NULL) {
            break;
        }
        switch (opcao[0]) {
            case '1': adicionar(agenda, &n); salvar(agenda, n); break;
            case '2': listar(agenda, n); break;
            case '3': buscar(agenda, n); break;
            case '4': remover(agenda, &n); salvar(agenda, n); break;
            case '0': rodando = 0; break;
            default: printf("Opção inválida.\n");
        }
    }
    printf("Até logo!\n");
    return 0;
}
O programa não carrega o arquivo, e a agenda abre vazia

O que aconteceu: quase sempre o programa foi rodado de uma pasta diferente da do agenda.txt. O fopen("agenda.txt", ...) procura o arquivo na pasta em que o programa roda, não na pasta do código.

Como resolver: rode o programa de dentro da pasta onde está o agenda.txt (cd ~/c/agenda). No editor do guia, o arquivo é a aba agenda.txt e fica sempre junto.


O programa completo

A versão final, igual à versão 4. Compile com gcc -std=c23 -Wall -Wextra agenda.c -o agenda:

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define MAX_CONTATOS 100
#define TAM_NOME 50
#define TAM_TEL 20
#define ARQUIVO "agenda.txt"

struct Contato {
    char nome[TAM_NOME];
    char telefone[TAM_TEL];
};

void tirar_novalinha(char *s) {
    s[strcspn(s, "\n")] = '\0';
}

int ler_linha(const char *rotulo, char *destino, int tam) {
    printf("%s", rotulo);
    if (fgets(destino, tam, stdin) == NULL) {
        return 0;
    }
    tirar_novalinha(destino);
    return 1;
}

int contem_ignorando_caso(const char *texto, const char *parte) {
    char t[TAM_NOME];
    char p[TAM_NOME];
    int i;
    for (i = 0; texto[i] != '\0' && i < TAM_NOME - 1; i++) {
        t[i] = (char) tolower((unsigned char) texto[i]);
    }
    t[i] = '\0';
    for (i = 0; parte[i] != '\0' && i < TAM_NOME - 1; i++) {
        p[i] = (char) tolower((unsigned char) parte[i]);
    }
    p[i] = '\0';
    return strstr(t, p) != NULL;
}

void adicionar(struct Contato agenda[], int *n) {
    if (*n >= MAX_CONTATOS) {
        printf("Agenda cheia.\n");
        return;
    }
    struct Contato c = {0};
    ler_linha("Nome: ", c.nome, TAM_NOME);
    ler_linha("Telefone: ", c.telefone, TAM_TEL);
    agenda[*n] = c;
    (*n)++;
    printf("Contato adicionado.\n");
}

void listar(const struct Contato agenda[], int n) {
    if (n == 0) {
        printf("Agenda vazia.\n");
        return;
    }
    printf("\n%-20s %s\n", "Nome", "Telefone");
    for (int i = 0; i < n; i++) {
        printf("%-20s %s\n", agenda[i].nome, agenda[i].telefone);
    }
}

void buscar(const struct Contato agenda[], int n) {
    char alvo[TAM_NOME];
    ler_linha("Buscar: ", alvo, TAM_NOME);
    int achou = 0;
    for (int i = 0; i < n; i++) {
        if (contem_ignorando_caso(agenda[i].nome, alvo)) {
            printf("%-20s %s\n", agenda[i].nome, agenda[i].telefone);
            achou = 1;
        }
    }
    if (!achou) {
        printf("Nenhum contato com \"%s\".\n", alvo);
    }
}

void remover(struct Contato agenda[], int *n) {
    char alvo[TAM_NOME];
    ler_linha("Remover (nome exato): ", alvo, TAM_NOME);
    for (int i = 0; i < *n; i++) {
        if (strcmp(agenda[i].nome, alvo) == 0) {
            for (int j = i; j < *n - 1; j++) {
                agenda[j] = agenda[j + 1];
            }
            (*n)--;
            printf("Contato removido.\n");
            return;
        }
    }
    printf("Contato \"%s\" não encontrado.\n", alvo);
}

void salvar(const struct Contato agenda[], int n) {
    FILE *f = fopen(ARQUIVO, "w");
    if (f == NULL) {
        perror(ARQUIVO);
        return;
    }
    for (int i = 0; i < n; i++) {
        fprintf(f, "%s;%s\n", agenda[i].nome, agenda[i].telefone);
    }
    fclose(f);
}

void carregar(struct Contato agenda[], int *n) {
    FILE *f = fopen(ARQUIVO, "r");
    if (f == NULL) {
        return;
    }
    char linha[TAM_NOME + TAM_TEL];
    while (*n < MAX_CONTATOS && fgets(linha, sizeof linha, f) != NULL) {
        if (sscanf(linha, "%49[^;];%19[^\n]", agenda[*n].nome, agenda[*n].telefone) == 2) {
            (*n)++;
        }
    }
    fclose(f);
}

int main(void) {
    struct Contato agenda[MAX_CONTATOS];
    int n = 0;
    carregar(agenda, &n);

    char opcao[10];
    int rodando = 1;
    while (rodando) {
        printf("\n=== Agenda da Ana ===\n");
        printf("1. Adicionar  2. Listar  3. Buscar  4. Remover  0. Sair\n");
        printf("Opção: ");
        if (fgets(opcao, sizeof opcao, stdin) == NULL) {
            break;
        }
        switch (opcao[0]) {
            case '1': adicionar(agenda, &n); salvar(agenda, n); break;
            case '2': listar(agenda, n); break;
            case '3': buscar(agenda, n); break;
            case '4': remover(agenda, &n); salvar(agenda, n); break;
            case '0': rodando = 0; break;
            default: printf("Opção inválida.\n");
        }
    }
    printf("Até logo!\n");
    return 0;
}

O que o projeto juntou: structs, um array e um contador, funções com passagem por ponteiro, fgets para texto com espaço, tolower e strstr/strcmp para a busca, o deslocamento do array na remoção e os arquivos para a agenda durar.


Desafios extras

  1. Editar um contato. Acrescente a opção "5. Editar": busca pelo nome exato e deixa a Ana trocar o telefone.
  2. Ordem alfabética. Antes de listar, ordene o array por nome com qsort e strcmp.
  3. Confirmar a remoção. Antes de remover, pergunte "Tem certeza? (s/n)" e só remova se a resposta for s.
  4. Agenda de qualquer tamanho. Troque o array fixo de 100 por um array que cresce com realloc quando enche (veja Array que Cresce).
  5. Telefone com ;. Pense no que acontece se um nome tiver ; e escolha outro separador, ou grave em outro formato.

Exercícios

1. Por que a agenda lê os nomes com fgets em vez de scanf("%s")?

2. Para remover o contato da posição i de um array de n contatos, o que o programa faz?

3. O que a busca faz para não diferenciar maiúsculas?

4. Por que a agenda chama salvar depois de adicionar e remover?

5. Complete o modo do fopen para escrever (criar/sobrescrever) o arquivo:

FILE *f = fopen("agenda.txt", "");

6. Complete a função que compara dois nomes e vale 0 quando são iguais:

if ((agenda[i].nome, alvo) == 0)

Resumo

  • Uma struct junta os dados de um contato; um array de structs e um contador guardam a agenda.
  • Ler tudo com fgets (inclusive a opção do menu) aceita nomes com espaço e evita o \n perdido do scanf.
  • Buscar sem diferenciar maiúsculas: passar os textos por tolower e usar strstr.
  • Remover de um array é deslocar os seguintes para trás e diminuir a contagem.
  • Gravar com fprintf e ler com fgets + sscanf (com larguras) faz a agenda durar depois de fechar o programa.

Próximo projeto: Estoque da Lojinha (binário).