📚 Capítulo do curso: 03 — Entrada e Saída Formatada e Buffers da Especialização em Sistemas com C. Leia o capítulo antes de começar.

⌨️ Miniprojeto C 03 — Terminal de Check-in e Sanitização de Buffers

Um check-in de terminal que lê nome, idade, altura e cidade do visitante e resolve, na prática, o bug clássico do buffer de entrada “sujo”, que faz um fgets parecer pular sozinho.

🔒 Regra de Ouro: até o Capítulo 03 você tem sequência, variáveis e tipos e, agora, E/S formatada com scanf, fgets, getchar e putchar. Ainda sem if, switch, laços ou funções extras. Por isso o check-in não consegue validar uma idade negativa ou um nome vazio. A validação chega no Miniprojeto 05, e repetir o check-in para vários visitantes, no Miniprojeto 06.

⏱️ Tempo estimado 📦 Você vai entregar ✅ Pronto quando
50 min main.c + entrada.txt nenhum campo sai vazio no resumo, digitando ou com a entrada redirecionada

Diagrama do ⌨️ Miniprojeto C 03 — Terminal de Check-in e Sanitização de Buffers

🎯 Objetivos

📋 O problema

Você está construindo o terminal de check-in da recepção de um evento técnico. Em sequência, ele lê o nome completo (fgets), a idade (scanf("%d")), a altura (scanf("%f")) e a cidade natal (fgets), e imprime um resumo.

ID Requisito
RF01 Compila sem nenhum aviso com gcc -Wall -Wextra -Wpedantic -std=c17.
RF02 Inclui <stdio.h>, <stdlib.h> e <string.h>, usa int main(void) e termina com return EXIT_SUCCESS;.
RF03 Nenhum if, switch, while, for ou função além de main.
RF04 Lê o nome com fgets e remove o \n da string (ex.: com strcspn).
RF05 Lê a idade com scanf("%d", ...) e a altura com scanf("%f", ...).
RF06 Usa getchar() para descartar o \n deixado pelo último scanf, logo antes do fgets seguinte.
RF07 Lê a cidade com fgets (removendo o \n) depois do getchar(), provando que a leitura não foi pulada.
RF08 Usa putchar() pelo menos uma vez.
RF09 O resumo final mostra os 4 campos com os valores corretos.
graph LR
    A["main() inicia"] --> B["fgets: nome\n(remove newline com strcspn)"]
    B --> C["scanf: idade (%d)"]
    C --> D["scanf: altura (%f)"]
    D --> E["getchar()\nconsome newline residual"]
    E --> F["fgets: cidade\n(remove newline com strcspn)"]
    F --> G["Resumo formatado\n(printf + putchar)"]
    G --> H["return EXIT_SUCCESS"]

    style D fill:#b45309,stroke:#f59e0b,color:#ffffff
    style E fill:#dc2626,stroke:#f87171,color:#ffffff
    style G fill:#059669,stroke:#34d399,color:#ffffff

🧰 Ambiente

O mesmo do Miniprojeto 01: gcc 8 ou mais novo. No Windows, rode chcp 65001 se os emojis aparecerem quebrados.


Passo 1 — Compilar o esqueleto

🎯 Objetivo: ver o resumo com os valores iniciais, antes de ler qualquer coisa do teclado.

⌨️ Faça: crie a pasta c_mini_03, salve o código abaixo como main.c e rode:

gcc -Wall -Wextra -Wpedantic -std=c17 main.c -o app
./app          # Linux/macOS
.\app.exe      # Windows (PowerShell)

📄 src/esqueleto.c

/*
 * Miniprojeto 03: Sistema de Terminal Interativo e Sanitizacao de Buffers
 * Regra: sequencia + E/S formatada + buffer residual + getchar/putchar.
 * Ainda SEM if, switch, lacos ou funcoes extras.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define TAM_MAX 80

int main(void) {
    // Valores iniciais: aparecem no resumo enquanto as leituras (TODOs) nao forem feitas
    char nome[TAM_MAX] = "(vazio)";
    char cidade[TAM_MAX] = "(vazio)";
    int idade = 0;
    float altura_m = 0.0f;

    printf("====================================================\n");
    printf(" TERMINAL DE CHECK-IN - DIGITE SEUS DADOS\n");
    printf("====================================================\n");

    printf("Nome completo: ");
    // TODO 1: leia o nome com fgets(nome, TAM_MAX, stdin)
    // TODO 2: remova o '\n' residual com nome[strcspn(nome, "\n")] = '\0';

    printf("Idade (anos): ");
    // TODO 3: leia a idade com scanf("%d", &idade)

    printf("Altura (m): ");
    // TODO 4: leia a altura com scanf("%f", &altura_m)

    // TODO 5: chame getchar() aqui para descartar o '\n' que o scanf
    // da altura deixou parado no buffer (senao o proximo fgets "pula")

    printf("Cidade natal: ");
    // TODO 6: leia a cidade com fgets e remova o '\n' residual (igual ao nome)

    putchar('\n');
    printf("====================================================\n");
    printf(" RESUMO DO CHECK-IN\n");
    printf("====================================================\n");
    printf("Nome    : %s\n", nome);
    printf("Idade   : %d anos\n", idade);
    printf("Altura  : %.2f m\n", altura_m);
    printf("Cidade  : %s\n", cidade);

    printf("====================================================\n");

    return EXIT_SUCCESS;
}

✅ Checkpoint: zero avisos, e o programa termina sozinho, sem esperar digitação:

🖥️ Saída esperada:

====================================================
 TERMINAL DE CHECK-IN - DIGITE SEUS DADOS
====================================================
Nome completo: Idade (anos): Altura (m): Cidade natal: 
====================================================
 RESUMO DO CHECK-IN
====================================================
Nome    : (vazio)
Idade   : 0 anos
Altura  : 0.00 m
Cidade  : (vazio)
====================================================

Repare que as perguntas saem todas na mesma linha: ainda não há leitura nenhuma entre elas.


Passo 2 — Ler o nome com fgets (TODO 1 e 2)

🎯 Objetivo: ler texto com espaços e entender o \n que vem junto.

⌨️ Faça:

  1. Leia o nome com fgets(nome, TAM_MAX, stdin);.
  2. Recompile, rode, digite Ana Maria e observe o resumo: a linha seguinte a Nome : Ana Maria fica em branco. É o \n do Enter, guardado dentro da string.
  3. Remova-o com nome[strcspn(nome, "\n")] = '\0'; e rode de novo.

✅ Checkpoint: Nome : Ana Maria e, na linha logo abaixo, Idade : 0 anos, sem linha em branco no meio.


Passo 3 — Ler idade e altura, e ver o bug acontecer (TODO 3, 4 e 6)

🎯 Objetivo: reproduzir o bug do buffer residual antes de corrigi-lo.

⌨️ Faça: leia a idade com scanf("%d", &idade);, a altura com scanf("%f", &altura_m); e a cidade com fgets + strcspn, igual ao nome. Ainda não faça o TODO 5. Rode e digite os dados.

✅ Checkpoint: depois de digitar a altura e apertar Enter, o programa não espera você digitar a cidade e o resumo mostra Cidade : vazio. O bug está reproduzido.

💡 O scanf("%f") leu 1.68 e parou no \n, que ficou esperando no buffer. O fgets da cidade encontrou esse \n sozinho e o tratou como uma linha vazia.

🧯 Se der errado: se o gcc avisar ignoring return value of 'scanf', o seu compilador está com _FORTIFY_SOURCE ligado. Pode seguir: conferir o retorno do scanf exige if, que só chega no Capítulo 05.


Passo 4 — Corrigir com getchar() (TODO 5)

🎯 Objetivo: consumir exatamente o \n pendente.

⌨️ Faça: no lugar do TODO 5, logo antes do printf("Cidade natal: "), chame getchar();. Rode e digite os dados de novo.

✅ Checkpoint: o programa espera a cidade, e o resumo mostra os 4 campos preenchidos.


Passo 5 — Testar com entrada redirecionada

🎯 Objetivo: testar sem digitar tudo de novo, como fazem os testes automatizados.

⌨️ Faça: na pasta do projeto, crie entrada.txt com uma resposta por linha:

📄 tests/main.entrada.txt

Ana Maria Souza
21
1.68
Porto Alegre
./app < entrada.txt                    # Linux/macOS
Get-Content entrada.txt | .\app.exe    # Windows (PowerShell)

✅ Checkpoint: com a entrada redirecionada, o teclado não “ecoa” o que foi digitado, então as perguntas saem coladas na mesma linha. O resumo, que é o que importa, tem que bater:

🖥️ Saída esperada:

====================================================
 ⚡ TERMINAL DE CHECK-IN - DIGITE SEUS DADOS
====================================================
Nome completo: Idade (anos): Altura (m): Cidade natal: 
====================================================
 📋 RESUMO DO CHECK-IN
====================================================
Nome    : Ana Maria Souza
Idade   : 21 anos
Altura  : 1.68 m
Cidade  : Porto Alegre
====================================================

Confira também o código de retorno (echo $? ou echo $LASTEXITCODE → 0).

🧯 Se der errado: se a cidade sair vazia só no teste redirecionado, confira se o entrada.txt termina com uma quebra de linha depois de Porto Alegre.

🔑 Ver gabarito: src/main.c

📄 src/main.c

/*
 * Miniprojeto 03: Sistema de Terminal Interativo e Sanitizacao de Buffers
 * Especializacao em Engenharia de Sistemas em C
 * Regra: sequencia + E/S formatada + buffer residual + getchar/putchar.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define TAM_MAX 80

int main(void) {
    char nome[TAM_MAX];
    char cidade[TAM_MAX];
    int idade;
    float altura_m;

    printf("====================================================\n");
    printf(" ⚡ TERMINAL DE CHECK-IN - DIGITE SEUS DADOS\n");
    printf("====================================================\n");

    printf("Nome completo: ");
    fgets(nome, TAM_MAX, stdin);
    // fgets guarda o '\n' digitado dentro da propria string; strcspn acha
    // a posicao do primeiro '\n' e a substituimos por '\0' (fim de string).
    nome[strcspn(nome, "\n")] = '\0';

    printf("Idade (anos): ");
    scanf("%d", &idade);

    printf("Altura (m): ");
    scanf("%f", &altura_m);

    // O ultimo scanf("%f", ...) LEU o numero mas deixou o '\n' (Enter)
    // parado no buffer de entrada. Se chamassemos fgets agora sem limpar
    // esse '\n' residual, o fgets leria uma linha vazia e "pularia" a
    // pergunta seguinte. getchar() consome exatamente esse '\n' pendente.
    getchar();

    printf("Cidade natal: ");
    fgets(cidade, TAM_MAX, stdin);
    cidade[strcspn(cidade, "\n")] = '\0';

    putchar('\n');
    printf("====================================================\n");
    printf(" 📋 RESUMO DO CHECK-IN\n");
    printf("====================================================\n");
    printf("Nome    : %s\n", nome);
    printf("Idade   : %d anos\n", idade);
    printf("Altura  : %.2f m\n", altura_m);
    printf("Cidade  : %s\n", cidade);
    printf("====================================================\n");

    return EXIT_SUCCESS;
}

Por que isso importa: scanf("%d") e scanf("%f") param no primeiro caractere que não é número, e o \n do Enter fica no buffer. Um scanf numérico seguinte não se importa, porque pula espaços em branco antes de converter. Já o fgets lê a linha inteira, inclusive esse \n solitário. Esse descompasso entre “quem ignora espaço em branco” e “quem não ignora” é o bug mais reportado por iniciantes em C.


🏆 Desafio

Leia mais um campo com fgets (por exemplo, o e-mail de contato) entre a idade e a altura.

✅ Checkpoint: o e-mail aparece no resumo e nenhum campo sai vazio. Dica: depois do scanf da idade vem um fgets, então você precisa de mais um getchar().

Para atender o próximo visitante, ainda é preciso executar o programa de novo. Repetir o check-in na mesma execução exige um laço, assunto do Miniprojeto 06.


🧭 Navegação

📋 Projetos do curso de C | 📚 Teoria do Capítulo 03 | 🗂️ Hub de projetos