Preservar cada linha do recibo

CBeginner
Pratique Agora

Introdução

A Sunny Snacks precisa de um recibo que preserve cada compra, mesmo quando um cliente compra o mesmo lanche novamente. Primeiro, você executará um exemplo completo com um item e depois completará um programa C que armazena compras separadas e imprime cada linha, seu preço unitário, seu subtotal e o total completo.

Você precisa conhecer variáveis, condições, laços, arrays e funções em C, além de entrada básica no terminal e caminhos. Este laboratório apresenta as estruturas e os detalhes de saída de texto necessários. O ambiente no navegador já inclui um compilador e o terminal Workspace. Os valores são centavos inteiros sintéticos, não um sistema de pagamento ou contabilidade. O tempo de leitura e prática é uma estimativa do autor; não foi medido com iniciantes.

Ler um recibo de um item

Nesta etapa, você executará um pequeno exemplo completo e mudará uma compra de um item para uma compra de dois itens.

Abra a aba Workspace. O terminal começa em /home/labex/project/receipts. cd muda o diretório; pwd mostra o atual e ls lista os arquivos. Esses comandos também permitem se recuperar de um caminho ou nome digitado incorretamente.

cd /home/labex/project/receipts
pwd
ls

sample.c é um exemplo completo de um item. till.c é o programa incompleto para vários itens da próxima etapa. O catálogo, o analisador e o controlador de entrada são arquivos preparados; não os altere. cat mostra um arquivo de texto sem editá-lo:

cat sample.c

A variável quantity do exemplo começa em 1. unit_cents vale 125: uma unidade monetária e 25 centavos. A multiplicação calcula um subtotal em centavos. Preserve essa unidade durante todo o cálculo. A divisão inteira por 100 fornece a parte inteira; % 100 fornece o resto. Em printf, %d imprime um inteiro, %s uma string C terminada, %02d preenche a parte fracionária até dois dígitos e \n encerra a linha. Assim, 250 centavos aparecem como 2.50 sem aritmética de ponto flutuante.

Compile o programa. gcc é o compilador C; -std=c11 seleciona C11, -Wall -Wextra ativa avisos e -o sample dá nome ao executável. ./sample executa esse arquivo a partir deste diretório.

gcc -std=c11 -Wall -Wextra sample.c -o sample
./sample

O resultado normal completo é:

SUNNY SNACKS
ID | ITEM | QTY | UNIT | SUBTOTAL
101 | Oat bar | 1 | 1.25 | 1.25
TOTAL | 1.25

ID significa identificador do produto: 101 corresponde a essa entrada do catálogo. UNIT é o preço de um item; SUBTOTAL é a quantidade desta compra multiplicada por seu preço unitário. As barras verticais separam colunas legíveis. O TOTAL final soma os subtotais de todas as compras; aqui há apenas uma.

Mude a quantidade para 2. O comando sed a seguir substitui o texto antigo exato pelo novo; -i salva a alteração. As aspas simples mantêm a substituição em um único argumento do shell.

sed -i 's/int quantity = 1;/int quantity = 2;/' sample.c

Recompile depois de editar: mudar o código-fonte não atualiza automaticamente um executável antigo. O operador do shell > redireciona a saída padrão para um arquivo. Ele abre ou trunca esse arquivo antes de o programa C começar; por isso, use o novo nome de saída indicado.

gcc -std=c11 -Wall -Wextra sample.c -o sample
./sample > sample-receipt.txt
cat sample-receipt.txt

A quantidade agora é 2 e tanto o subtotal quanto o total são 2.50; o preço unitário continua 1.25. Confira à mão: 125 × 2 = 250 centavos. Preserve sample.c, o executável sample e sample-receipt.txt.

Se faltar um arquivo, volte com o cd aprendido e use ls para conferir seu nome. Se o compilador apontar um erro, corrija a linha indicada e recompile antes de executar; um executável antigo pode continuar existindo após uma compilação malsucedida.

Preservar e imprimir cada compra

Nesta etapa, você preservará compras como registros separados e produzirá um recibo completo com três linhas de compra.

Comece em Workspace, em /home/labex/project/receipts. Leia o catálogo e o programa incompleto:

cd /home/labex/project/receipts
cat catalog.h
cat till.c

O arquivo de cabeçalho catalog.h fornece o nome do balcão, o máximo de seis compras, a quantidade máxima de 20 e quatro entradas de catálogo. #include "catalog.h" incorpora essas definições durante a compilação. #define dá nome a uma constante ou um inicializador; CATALOG_ROWS fornece as linhas que inicializam o array de produtos. Uma barra invertida (\) no fim de uma linha de definição do catálogo continua o mesmo #define na próxima linha física. Preserve catalog.h, parse.h, driver.h e read-line.h sem alterações neste laboratório.

Uma estrutura reúne campos relacionados em um registro. Product reúne um id inteiro, um array de 32 caracteres para o nome e um preço inteiro cents. typedef permite usar Product como nome curto desse tipo de registro. Uma string C termina com o caractere de valor zero \0; portanto, o array do nome precisa de espaço para o terminador além dos caracteres visíveis. Os nomes fornecidos cabem; esta tarefa não analisa nomes arbitrários.

Purchase é um segundo tipo de registro. Ele preserva a posição do produto no array e a quantidade dessa compra. Em basket[line_count].quantity, os colchetes selecionam uma compra e o ponto seleciona seu campo de quantidade. As posições começam em zero. O array global static e line_count são inicializados em zero. sizeof products / sizeof products[0] calcula quantos registros completos de produto existem no array.

O laço fornecido find_product busca por ID do produto e retorna sua posição no array, ou -1 se ele não existir. Os dois produtos chamados Water têm IDs e preços diferentes: 103 custa 75 centavos e 104 é gratuito. O nome sozinho não identifica um produto. O catálogo tem quatro produtos, IDs de 1 a 999 e preços de 0 a 9999 centavos. O mesmo programa também deve funcionar quando recompilado com outro catálogo fornecido de quatro linhas dentro desses limites.

Complete approve_purchase verificando ID, quantidade e capacidade restante antes de escrever no array. Só então salve a posição do produto e a quantidade em basket[line_count] e incremente o contador. A quantidade vai de 1 a 20; preço zero é válido. Não una IDs repetidos: uma compra posterior é outra linha. Seis compras com o maior preço e quantidade permitidos somam no máximo 6 × 20 × 9999 = 1,199,880 centavos, dentro de um inteiro de 32 bits com sinal.

puts imprime uma string terminada e acrescenta uma quebra de linha. printf usa marcadores de formato, como %s para uma string terminada em NUL e %d para um inteiro. print_receipt percorre todas as compras aprovadas. Product product = ... copia esse registro do catálogo para uma variável local. Calcule cada subtotal inteiro, some ao total e imprima as linhas completas em ordem entre o título e o cabeçalho e um único TOTAL final. Use os cinco campos separados por barras verticais; espaços ao redor dos campos e linhas em branco podem variar, mas um recibo com registros extras ou campos ausentes não está completo.

Substitua till.c pelo código completo a seguir. cat > till.c substitui o arquivo; <<'EOF' inicia um here-document. O delimitador entre aspas copia literalmente o texto C até a linha que contém apenas EOF. Copie o bloco inteiro, incluindo essa última linha.

cat > till.c <<'EOF'
#include <stdio.h>
#include "catalog.h"
#include "parse.h"

typedef struct {
    int id;
    char name[32];
    int cents;
} Product;

typedef struct {
    int product_index;
    int quantity;
} Purchase;

static const Product products[] = CATALOG_ROWS;
static const int product_count = sizeof products / sizeof products[0];
static Purchase basket[MAX_LINES];
static int line_count;

static int find_product(int id) {
    for (int i = 0; i < product_count; i++)
        if (products[i].id == id) return i;
    return -1;
}

static int approve_purchase(int id, int quantity) {
    int index = find_product(id);
    if (index < 0 || quantity < 1 || quantity > MAX_QUANTITY || line_count >= MAX_LINES)
        return 0;
    basket[line_count].product_index = index;
    basket[line_count].quantity = quantity;
    line_count++;
    return 1;
}

static void print_receipt(void) {
    int total = 0;
    puts(SHOP_TITLE);
    puts("ID | ITEM | QTY | UNIT | SUBTOTAL");
    for (int i = 0; i < line_count; i++) {
        Product product = products[basket[i].product_index];
        int quantity = basket[i].quantity;
        int subtotal = product.cents * quantity;
        total += subtotal;
        printf("%d | %s | %d | %d.%02d | %d.%02d\n",
               product.id, product.name, quantity,
               product.cents / 100, product.cents % 100,
               subtotal / 100, subtotal % 100);
    }
    printf("TOTAL | %d.%02d\n", total / 100, total % 100);
}

#include "driver.h"
EOF

parse.h e driver.h fornecem o laço de entrada que já funciona. O controlador pede um ID e uma quantidade e chama suas funções de aprovação e impressão. Seus nomes e assinaturas existentes são pontos de integração; mantenha-os como aparecem. Os nomes internos dos tipos de registro e das variáveis locais não são o objetivo da avaliação. No próximo laboratório, você aprenderá o limite da análise de entrada.

Compile o programa completo:

gcc -std=c11 -Wall -Wextra till.c -o till

O controlador envia os prompts e as mensagens para a saída de erro padrão, que continua visível no terminal. Apenas o recibo concluído vai para a saída padrão. Execute-o com um novo arquivo de saída:

./till > receipt.txt

O programa está esperando entrada. Digite cada valor da tabela no prompt correspondente, pressionando Enter após cada um. Não os digite como comandos do shell. done é o comando exato de finalização, em minúsculas, e só funciona no prompt Product ID.

Entrada de Product ID Entrada de Quantity Resultado
101 2 Added line 1
102 1 Added line 2
101 1 Added line 3, preservando a compra anterior de 101
done Quantidade não solicitada Concluir o recibo e voltar ao shell

Você ainda vê os prompts e as mensagens Added line porque o controlador os escreve na saída de erro padrão, um fluxo separado. Só o recibo completo vai para a saída padrão, que > envia para receipt.txt. Quando o prompt do shell voltar, leia o recibo realmente salvo:

cat receipt.txt

Conteúdo esperado:

SUNNY SNACKS
ID | ITEM | QTY | UNIT | SUBTOTAL
101 | Oat bar | 2 | 1.25 | 2.50
102 | Fruit cup | 1 | 2.30 | 2.30
101 | Oat bar | 1 | 1.25 | 1.25
TOTAL | 6.05

As duas compras de Oat bar continuam separadas. Some seus subtotais com Fruit cup: 250 + 230 + 125 = 605 centavos. Um total aparentemente correto sem todas essas linhas perderia os detalhes da transação do cliente.

Os números usam dígitos decimais, um sinal opcional e espaços ASCII ou tabulações opcionais no início e no fim; zeros à esquerda são permitidos. Os IDs devem existir no catálogo, as quantidades devem ser 1..20 e cada linha deve ter no máximo 63 bytes antes de Enter. Rejeitar um ID ou uma quantidade descarta o candidato pendente e volta a Product ID sem alterar as compras aprovadas. O leitor fornecido consome uma linha longa demais em vez de dividi-la em respostas posteriores. São aprovadas no máximo seis compras. Uma cesta vazia seguida de done produz um recibo vazio válido com total 0.00.

Para cancelar em vez de finalizar, pressione Ctrl+D em um prompt de entrada vazio. Esse sinal de fim de arquivo, ou EOF durante uma linha inacabada, cancela com status 2 e sem recibo completo. Não exporta nem preserva a transação inacabada. O redirecionamento já abriu o arquivo de saída; use outro nome ao testar um cancelamento intencional. Digitar done como quantidade é texto de quantidade inválido, não uma finalização.

Se o recibo contiver apenas a última compra, confira se você salva em basket[line_count] e incrementa o contador após cada aprovação. Salve, recompile e execute uma nova transação com as mesmas compras exigidas. Cada execução começa com uma cesta vazia; não recarrega texto de um recibo antigo. Preserve till.c, o executável till, os arquivos fornecidos e o receipt.txt completo. A verificação do curso também executa cópias intactas do código com detecção de limites em tempo de execução em um diretório descartável. Pode detectar erros exercitados por essas entradas; não demonstra segurança completa de memória nem repara seus arquivos.

Resumo

Você transformou um exemplo de um item em um recibo completo com várias linhas. Usou estruturas, um array de compras separadas, IDs estáveis do catálogo e cálculos em centavos inteiros; depois salvou e leu o resultado real. O próximo laboratório se concentra na validação da entrada antes que uma compra possa entrar nesse array.