Aula 40 — Projeto Integrador

Sistema de Cadastro de Alunos — struct · lista encadeada · arquivo binário · menu interativo

1. O Projeto

Esta aula une tudo que foi estudado nas 39 aulas anteriores em um único programa funcional: um sistema de cadastro de alunos para console que roda no Windows (ou em qualquer terminal).

O programa mantém uma lista de alunos em memória (lista encadeada dinâmica), oferece um menu interativo de operações, e persiste os dados em arquivo binário — carregando automaticamente ao iniciar e salvando ao encerrar. Os dados sobrevivem entre execuções.

Funcionalidades

OpçãoOperaçãoConceitos usados
1Inserir alunomalloc, lista encadeada, struct
2Listar todospercurso da lista, printf formatado
3Buscar por matrículabusca sequencial, ponteiros
4Remover alunoremoção em lista encadeada, free
5Salvar em arquivofwrite, arquivo binário
6Carregar de arquivofread, fseek, ftell
0Salvar e sairliberação de memória, fclose
Como ler esta aula: O código é apresentado em blocos, cada um com explicação detalhada. No final, o código completo é reunido para facilitar a cópia e execução. Recomenda-se digitar cada bloco manualmente para assimilar melhor.

2. Bloco 1 — Tipos e Estruturas de Dados

O programa define dois tipos: a struct que representa um aluno e o nó da lista encadeada.

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

/* ─── Dados de um aluno ──────────────────────────────────────── */
typedef struct {
    int   matricula;     // chave de busca e ordenação
    char  nome[60];      // nome com espaços — funciona em binário
    float nota;          // 0.0 a 10.0
} Aluno;

/* ─── Nó da lista encadeada ─────────────────────────────────── */
typedef struct No {
    Aluno      aluno;    // os dados
    struct No *prox;     // ponteiro para o próximo nó
} No;

/* ─── Cabeça da lista ───────────────────────────────────────── */
No *lista = NULL;        // começa vazia

/* ─── Nome do arquivo de persistência ──────────────────────── */
const char *ARQUIVO = "cadastro.bin";
ElementoPor que assim
nome[60]Tamanho fixo dentro da struct → fwrite/fread gravam o nome completo, com espaços, sem complicação
No *lista = NULLLista começa vazia — NULL é o sentinela de fim de lista
const char *ARQUIVONome do arquivo em uma constante — fácil de mudar em um só lugar

3. Bloco 2 — Inserção na Lista

Novos alunos são inseridos em ordem crescente de matrícula — a lista sempre fica ordenada, o que facilita a exibição e a busca.

/* Insere um aluno mantendo a lista ordenada por matrícula.
   Retorna 1 em sucesso, 0 se a matrícula já existe. */
int inserir(Aluno a) {
    /* 1. Verifica duplicata */
    No *atual = lista;
    while (atual != NULL) {
        if (atual->aluno.matricula == a.matricula) {
            printf("Matrícula %d já cadastrada.\n", a.matricula);
            return 0;
        }
        atual = atual->prox;
    }

    /* 2. Aloca novo nó */
    No *novo = (No *)malloc(sizeof(No));
    if (novo == NULL) { printf("Memória insuficiente.\n"); return 0; }
    novo->aluno = a;
    novo->prox  = NULL;

    /* 3. Inserção na posição correta (ordem crescente de matrícula) */
    if (lista == NULL || a.matricula < lista->aluno.matricula) {
        // inserir na cabeça
        novo->prox = lista;
        lista = novo;
    } else {
        // encontra o nó antes da posição de inserção
        No *ant = lista;
        while (ant->prox != NULL &&
               ant->prox->aluno.matricula < a.matricula) {
            ant = ant->prox;
        }
        novo->prox = ant->prox;
        ant->prox  = novo;
    }
    return 1;
}
Por que manter a lista ordenada? A listagem fica sempre organizada para o usuário sem precisar ordenar na hora de exibir. Em um cadastro real, isso é muito mais importante do que a pequena complexidade extra na inserção.

4. Bloco 3 — Listar e Buscar

/* Exibe todos os alunos com cabeçalho formatado */
void listar() {
    if (lista == NULL) {
        printf("Nenhum aluno cadastrado.\n");
        return;
    }

    printf("\n%-8s | %-25s | %-5s | %s\n",
           "Matr.", "Nome", "Nota", "Situação");
    printf("%-8s-+-%-25s-+-%-5s-+-%s\n",
           "--------", "-------------------------", "-----", "---------");

    No *atual = lista;
    while (atual != NULL) {
        Aluno *a = &atual->aluno;
        printf("%-8d | %-25s | %-5.1f | %s\n",
               a->matricula, a->nome, a->nota,
               a->nota >= 5.0f ? "Aprovado" : "Reprovado");
        atual = atual->prox;
    }
    printf("\n");
}

/* Retorna ponteiro para o nó com a matrícula buscada, ou NULL */
No *buscarPorMatricula(int matricula) {
    No *atual = lista;
    while (atual != NULL) {
        if (atual->aluno.matricula == matricula)
            return atual;
        atual = atual->prox;
    }
    return NULL;
}
Retornar o ponteiro, não uma cópia: buscarPorMatricula retorna No * em vez de Aluno. Isso permite tanto ler quanto modificar os dados do nó encontrado — e é também necessário para a remoção, que precisa do nó anterior.

5. Bloco 4 — Remoção

A remoção em lista encadeada exige encontrar o nó anterior ao que será removido para reparar o elo:

/* Remove o aluno com a matrícula indicada.
   Retorna 1 se removido, 0 se não encontrado. */
int remover(int matricula) {
    if (lista == NULL) return 0;

    /* Caso especial: remover a cabeça */
    if (lista->aluno.matricula == matricula) {
        No *remover = lista;
        lista = lista->prox;   // avança a cabeça
        free(remover);
        return 1;
    }

    /* Caso geral: encontrar o nó anterior */
    No *ant = lista;
    while (ant->prox != NULL &&
           ant->prox->aluno.matricula != matricula) {
        ant = ant->prox;
    }

    if (ant->prox == NULL) return 0;     // não encontrado

    No *alvo = ant->prox;
    ant->prox = alvo->prox;              // repara o elo
    free(alvo);                          // libera a memória
    return 1;
}

/* Libera todos os nós da lista — chamada ao encerrar o programa */
void liberarLista() {
    No *atual = lista;
    while (atual != NULL) {
        No *prox = atual->prox;
        free(atual);
        atual = prox;
    }
    lista = NULL;
}

6. Bloco 5 — Persistência em Arquivo Binário

Salvar percorre a lista e grava cada Aluno com fwrite. Carregar lê as structs de volta com fread e as reinseere na lista:

/* Salva todos os alunos da lista no arquivo binário.
   Retorna o número de alunos gravados. */
int salvar() {
    FILE *f = fopen(ARQUIVO, "wb");
    if (f == NULL) {
        printf("Erro ao abrir '%s' para escrita.\n", ARQUIVO);
        return -1;
    }

    int count = 0;
    No *atual = lista;
    while (atual != NULL) {
        fwrite(&atual->aluno, sizeof(Aluno), 1, f);  // grava a struct
        count++;
        atual = atual->prox;
    }

    fclose(f);
    return count;
}

/* Carrega alunos do arquivo binário para a lista.
   Retorna o número de alunos carregados, ou -1 se o arquivo não existe. */
int carregar() {
    FILE *f = fopen(ARQUIVO, "rb");
    if (f == NULL) return -1;            // arquivo ainda não existe — tudo bem

    /* descobre quantas structs tem no arquivo */
    fseek(f, 0, SEEK_END);
    long bytes = ftell(f);
    rewind(f);
    int total = (int)(bytes / sizeof(Aluno));

    int carregados = 0;
    Aluno a;
    while (fread(&a, sizeof(Aluno), 1, f) == 1) {
        if (inserir(a)) carregados++;
    }

    fclose(f);
    return carregados;
}
Por que recarregar via inserir() em vez de reconstruir a lista diretamente? Porque inserir já trata duplicatas, mantém a ordem e aloca os nós corretamente. Reutilizar a função existente é mais seguro e elimina código duplicado.

7. Bloco 6 — Menu Interativo e main()

/* Limpa o buffer de entrada após scanf */
void limparBuffer() {
    int c;
    while ((c = getchar()) != '\n' && c != EOF);
}

/* Exibe o menu e retorna a opção escolhida */
int menu() {
    int op;
    printf("\n╔══════════════════════════════════╗\n");
    printf("║   SISTEMA DE CADASTRO — ALUNOS  ║\n");
    printf("╠══════════════════════════════════╣\n");
    printf("║  1. Inserir aluno               ║\n");
    printf("║  2. Listar todos                ║\n");
    printf("║  3. Buscar por matrícula        ║\n");
    printf("║  4. Remover aluno               ║\n");
    printf("║  5. Salvar em arquivo           ║\n");
    printf("║  6. Recarregar do arquivo       ║\n");
    printf("║  0. Salvar e sair               ║\n");
    printf("╚══════════════════════════════════╝\n");
    printf("Opção: ");
    scanf("%d", &op);
    limparBuffer();
    return op;
}

int main() {
    /* Carrega dados persistidos (se existirem) */
    int carregados = carregar();
    if (carregados > 0)
        printf("✔ %d aluno(s) carregado(s) de '%s'\n", carregados, ARQUIVO);
    else if (carregados == -1)
        printf("Nenhum arquivo anterior. Começando vazio.\n");

    int op;
    do {
        op = menu();

        if (op == 1) {
            /* ── Inserir ─────────────────────────────── */
            Aluno a;
            printf("Matrícula: "); scanf("%d",  &a.matricula); limparBuffer();
            printf("Nome:      "); fgets(a.nome, sizeof(a.nome), stdin);
            a.nome[strcspn(a.nome, "\n")] = '\0'; // remove \n
            printf("Nota:      "); scanf("%f",  &a.nota);      limparBuffer();

            if (inserir(a))
                printf("✔ Aluno %d inserido.\n", a.matricula);

        } else if (op == 2) {
            /* ── Listar ──────────────────────────────── */
            listar();

        } else if (op == 3) {
            /* ── Buscar ──────────────────────────────── */
            int mat;
            printf("Matrícula a buscar: "); scanf("%d", &mat); limparBuffer();
            No *encontrado = buscarPorMatricula(mat);
            if (encontrado) {
                Aluno *a = &encontrado->aluno;
                printf("Encontrado: %d | %s | %.1f | %s\n",
                       a->matricula, a->nome, a->nota,
                       a->nota >= 5.0f ? "Aprovado" : "Reprovado");
            } else {
                printf("Matrícula %d não encontrada.\n", mat);
            }

        } else if (op == 4) {
            /* ── Remover ─────────────────────────────── */
            int mat;
            printf("Matrícula a remover: "); scanf("%d", &mat); limparBuffer();
            if (remover(mat))
                printf("✔ Aluno %d removido.\n", mat);
            else
                printf("Matrícula %d não encontrada.\n", mat);

        } else if (op == 5) {
            /* ── Salvar ──────────────────────────────── */
            int n = salvar();
            if (n >= 0)
                printf("✔ %d aluno(s) salvos em '%s'.\n", n, ARQUIVO);

        } else if (op == 6) {
            /* ── Recarregar ──────────────────────────── */
            liberarLista();
            lista = NULL;
            int n = carregar();
            if (n >= 0)
                printf("✔ %d aluno(s) recarregado(s).\n", n);
            else
                printf("Arquivo '%s' não encontrado.\n", ARQUIVO);

        } else if (op != 0) {
            printf("Opção inválida.\n");
        }

    } while (op != 0);

    /* Salva automaticamente e libera memória */
    int n = salvar();
    if (n >= 0) printf("✔ Dados salvos (%d aluno(s)). Até logo!\n", n);
    liberarLista();
    return 0;
}

8. Exemplo de Sessão Completa

Nenhum arquivo anterior. Começando vazio.

╔══════════════════════════════════╗
║ SISTEMA DE CADASTRO — ALUNOS ║
╠══════════════════════════════════╣
║ 1. Inserir aluno ║
║ ... ║
╚══════════════════════════════════╝
Opção: 1
Matrícula: 1002
Nome: Bruno Lima
Nota: 6.0
✔ Aluno 1002 inserido.

Opção: 1
Matrícula: 1001
Nome: Ana Silva
Nota: 8.5
✔ Aluno 1001 inserido.

Opção: 2

Matr. | Nome | Nota | Situação
---------+---------------------------+-------+---------
1001 | Ana Silva | 8.5 | Aprovado
1002 | Bruno Lima | 6.0 | Aprovado

Opção: 3
Matrícula a buscar: 1002
Encontrado: 1002 | Bruno Lima | 6.0 | Aprovado

Opção: 0
✔ Dados salvos (2 aluno(s)). Até logo!

Na próxima execução do programa, os dados são carregados automaticamente de cadastro.bin:

✔ 2 aluno(s) carregado(s) de 'cadastro.bin'

9. Mapa de Conceitos — O que cada aula contribuiu

Funcionalidade do projetoAulas que a sustentam
Declaração de Aluno e No com typedefAulas 27, 29, 30
Lista encadeada: inserir, remover, percorrerAulas 37, 38
Ponteiros No *, operador ->Aulas 20, 28, 31
malloc, free, verificação de NULLAulas 22, 38
Menu com switch/do-while, scanf, fgetsAulas 09, 10, 03, 26
Funções com retorno e ponteirosAulas 13, 14
fwrite, fread, fseek, ftell (arquivo binário)Aula 39b
Modo "wb" / "rb", fopen/fcloseAula 39a
strcspn para remover \n de fgetsAula 26
Quer ir além? Algumas extensões naturais deste projeto: (1) adicionar busca por nome com strstr; (2) permitir edição de nota sem remover e reinserir; (3) exportar a lista para um arquivo .csv texto; (4) ordenar por nota ao listar; (5) adicionar mais campos à struct (curso, data de nascimento).

10. Código Completo — para compilar e testar

Cole tudo em um único arquivo cadastro.c e compile com:

gcc cadastro.c -o cadastro
.\cadastro.exe     // Windows
./cadastro         // Linux/macOS
/* ═══════════════════════════════════════════════════════════════
   Sistema de Cadastro de Alunos — Projeto Integrador
   AFF Education — Curso de C para Iniciantes
   Conceitos: struct · lista encadeada · malloc/free · fread/fwrite
═══════════════════════════════════════════════════════════════ */

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

typedef struct {
    int   matricula;
    char  nome[60];
    float nota;
} Aluno;

typedef struct No {
    Aluno      aluno;
    struct No *prox;
} No;

No *lista = NULL;
const char *ARQUIVO = "cadastro.bin";

/* ── Inserção ordenada por matrícula ────────────────────────── */
int inserir(Aluno a) {
    No *atual = lista;
    while (atual != NULL) {
        if (atual->aluno.matricula == a.matricula) {
            printf("Matrícula %d já cadastrada.\n", a.matricula);
            return 0;
        }
        atual = atual->prox;
    }
    No *novo = (No *)malloc(sizeof(No));
    if (novo == NULL) { printf("Memória insuficiente.\n"); return 0; }
    novo->aluno = a;
    novo->prox  = NULL;
    if (lista == NULL || a.matricula < lista->aluno.matricula) {
        novo->prox = lista;
        lista = novo;
    } else {
        No *ant = lista;
        while (ant->prox != NULL &&
               ant->prox->aluno.matricula < a.matricula)
            ant = ant->prox;
        novo->prox = ant->prox;
        ant->prox  = novo;
    }
    return 1;
}

/* ── Listagem ───────────────────────────────────────────────── */
void listar() {
    if (lista == NULL) { printf("Nenhum aluno cadastrado.\n"); return; }
    printf("\n%-8s | %-25s | %-5s | %s\n",
           "Matr.", "Nome", "Nota", "Situação");
    printf("%-8s-+-%-25s-+-%-5s-+-%s\n",
           "--------","-------------------------","-----","---------");
    No *atual = lista;
    while (atual != NULL) {
        Aluno *a = &atual->aluno;
        printf("%-8d | %-25s | %-5.1f | %s\n",
               a->matricula, a->nome, a->nota,
               a->nota >= 5.0f ? "Aprovado" : "Reprovado");
        atual = atual->prox;
    }
    printf("\n");
}

/* ── Busca ──────────────────────────────────────────────────── */
No *buscarPorMatricula(int matricula) {
    No *atual = lista;
    while (atual != NULL) {
        if (atual->aluno.matricula == matricula) return atual;
        atual = atual->prox;
    }
    return NULL;
}

/* ── Remoção ────────────────────────────────────────────────── */
int remover(int matricula) {
    if (lista == NULL) return 0;
    if (lista->aluno.matricula == matricula) {
        No *rem = lista; lista = lista->prox; free(rem); return 1;
    }
    No *ant = lista;
    while (ant->prox != NULL &&
           ant->prox->aluno.matricula != matricula)
        ant = ant->prox;
    if (ant->prox == NULL) return 0;
    No *alvo = ant->prox;
    ant->prox = alvo->prox;
    free(alvo);
    return 1;
}

/* ── Liberação total ────────────────────────────────────────── */
void liberarLista() {
    No *atual = lista;
    while (atual != NULL) {
        No *prox = atual->prox; free(atual); atual = prox;
    }
    lista = NULL;
}

/* ── Salvar em arquivo binário ─────────────────────────────── */
int salvar() {
    FILE *f = fopen(ARQUIVO, "wb");
    if (f == NULL) {
        printf("Erro ao salvar em '%s'.\n", ARQUIVO); return -1;
    }
    int count = 0;
    No *atual = lista;
    while (atual != NULL) {
        fwrite(&atual->aluno, sizeof(Aluno), 1, f);
        count++;
        atual = atual->prox;
    }
    fclose(f);
    return count;
}

/* ── Carregar do arquivo binário ──────────────────────────── */
int carregar() {
    FILE *f = fopen(ARQUIVO, "rb");
    if (f == NULL) return -1;
    int carregados = 0;
    Aluno a;
    while (fread(&a, sizeof(Aluno), 1, f) == 1)
        if (inserir(a)) carregados++;
    fclose(f);
    return carregados;
}

/* ── Utilitários ────────────────────────────────────────────── */
void limparBuffer() { int c; while ((c=getchar())!='\n'&&c!=EOF); }

int menu() {
    int op;
    printf("\n╔══════════════════════════════════╗\n");
    printf("║   SISTEMA DE CADASTRO — ALUNOS  ║\n");
    printf("╠══════════════════════════════════╣\n");
    printf("║  1. Inserir aluno               ║\n");
    printf("║  2. Listar todos                ║\n");
    printf("║  3. Buscar por matrícula        ║\n");
    printf("║  4. Remover aluno               ║\n");
    printf("║  5. Salvar em arquivo           ║\n");
    printf("║  6. Recarregar do arquivo       ║\n");
    printf("║  0. Salvar e sair               ║\n");
    printf("╚══════════════════════════════════╝\n");
    printf("Opção: ");
    scanf("%d", &op);
    limparBuffer();
    return op;
}

/* ── Main ────────────────────────────────────────────────────── */
int main() {
    int carregados = carregar();
    if (carregados > 0)
        printf("✔ %d aluno(s) carregado(s) de '%s'\n", carregados, ARQUIVO);
    else
        printf("Nenhum arquivo anterior. Começando vazio.\n");

    int op;
    do {
        op = menu();
        if (op == 1) {
            Aluno a;
            printf("Matrícula: "); scanf("%d", &a.matricula); limparBuffer();
            printf("Nome:      "); fgets(a.nome, sizeof(a.nome), stdin);
            a.nome[strcspn(a.nome, "\n")] = '\0';
            printf("Nota:      "); scanf("%f", &a.nota); limparBuffer();
            if (inserir(a)) printf("✔ Aluno %d inserido.\n", a.matricula);
        } else if (op == 2) {
            listar();
        } else if (op == 3) {
            int mat;
            printf("Matrícula: "); scanf("%d", &mat); limparBuffer();
            No *n = buscarPorMatricula(mat);
            if (n) {
                Aluno *a = &n->aluno;
                printf("Encontrado: %d | %s | %.1f | %s\n",
                       a->matricula, a->nome, a->nota,
                       a->nota >= 5.0f ? "Aprovado" : "Reprovado");
            } else printf("Matrícula %d não encontrada.\n", mat);
        } else if (op == 4) {
            int mat;
            printf("Matrícula: "); scanf("%d", &mat); limparBuffer();
            if (remover(mat)) printf("✔ Aluno %d removido.\n", mat);
            else printf("Matrícula %d não encontrada.\n", mat);
        } else if (op == 5) {
            int n = salvar();
            if (n >= 0) printf("✔ %d aluno(s) salvos.\n", n);
        } else if (op == 6) {
            liberarLista();
            int n = carregar();
            if (n >= 0) printf("✔ %d aluno(s) recarregado(s).\n", n);
            else printf("Arquivo não encontrado.\n");
        } else if (op != 0) {
            printf("Opção inválida.\n");
        }
    } while (op != 0);

    int n = salvar();
    if (n >= 0) printf("✔ Dados salvos (%d aluno(s)). Até logo!\n", n);
    liberarLista();
    return 0;
}