Conservar cada línea del recibo

CBeginner
Practicar Ahora

Introducción

Sunny Snacks necesita un recibo que conserve cada compra, incluso cuando un cliente vuelve a comprar el mismo aperitivo. Primero ejecutarás un ejemplo de un artículo y después completarás un programa C que almacena compras separadas e imprime cada línea, su precio unitario, su subtotal y el total completo.

Necesitas conocer variables, condiciones, bucles, arrays y funciones de C, además de la entrada básica de terminal y las rutas. Este laboratorio introduce las estructuras y los detalles de salida de texto necesarios. El entorno del navegador ya incluye un compilador y el terminal Workspace. Los importes son centavos enteros sintéticos, no un sistema de pagos o contabilidad. El tiempo de lectura y práctica es una estimación del autor; no se ha medido con principiantes.

Leer un recibo de un artículo

En este paso ejecutarás un pequeño ejemplo completo y cambiarás una compra de un artículo por una de dos artículos.

Abre la pestaña Workspace. El terminal empieza en /home/labex/project/receipts. cd cambia de directorio; pwd muestra el actual y ls enumera los archivos. Estos comandos también permiten recuperarte de una ruta o un nombre mal escrito.

cd /home/labex/project/receipts
pwd
ls

sample.c es un ejemplo completo de un artículo. till.c es el programa incompleto de varios artículos del siguiente paso. El catálogo, el analizador y el controlador de entrada son archivos preparados; no los cambies. cat muestra un archivo de texto sin editarlo:

cat sample.c

La variable quantity del ejemplo empieza en 1. unit_cents vale 125: una unidad monetaria y 25 centavos. La multiplicación calcula un subtotal en centavos. Conserva esa unidad durante todo el cálculo. La división entera por 100 da la parte entera; % 100 da el resto. En printf, %d imprime un entero, %s una cadena C terminada, %02d rellena la parte fraccionaria hasta dos dígitos y \n termina la línea. Así, 250 centavos se muestran como 2.50 sin aritmética de coma flotante.

Compila el programa. gcc es el compilador de C; -std=c11 selecciona C11, -Wall -Wextra activa advertencias y -o sample da nombre al ejecutable. ./sample ejecuta ese archivo desde este directorio.

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

El resultado normal completo es:

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

ID significa identificador de producto: 101 corresponde a esta entrada del catálogo. UNIT es el precio de un artículo; SUBTOTAL es la cantidad de esta compra multiplicada por su precio unitario. Las barras verticales separan columnas legibles. El TOTAL final suma los subtotales de todas las compras; aquí solo hay una.

Cambia la cantidad a 2. El siguiente comando sed sustituye el texto antiguo exacto por el nuevo; -i guarda el cambio. Las comillas simples mantienen la sustitución como un único argumento del shell.

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

Recompila después de editar: cambiar el código fuente no actualiza automáticamente un ejecutable antiguo. El operador del shell > redirige la salida estándar a un archivo. Lo abre o trunca antes de que empiece el programa C; por eso debes usar el nuevo nombre de salida indicado.

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

La cantidad es ahora 2 y tanto el subtotal como el total son 2.50; el precio unitario sigue siendo 1.25. Compruébalo a mano: 125 × 2 = 250 centavos. Conserva sample.c, el ejecutable sample y sample-receipt.txt.

Si falta un archivo, vuelve con el cd que ya aprendiste y usa ls para comprobar su nombre. Si el compilador señala un error, corrige la línea indicada y recompila antes de ejecutar; un ejecutable antiguo puede seguir existiendo tras una compilación fallida.

Conservar e imprimir cada compra

En este paso conservarás compras como registros separados y generarás un recibo completo de tres líneas.

Empieza en Workspace, en /home/labex/project/receipts. Lee el catálogo y el programa incompleto:

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

El archivo de cabecera catalog.h proporciona el nombre del puesto, el máximo de seis compras, la cantidad máxima de 20 y cuatro entradas de catálogo. #include "catalog.h" incorpora esas definiciones al compilar. #define da nombre a una constante o un inicializador; CATALOG_ROWS proporciona las filas que inicializan el array de productos. Una barra inversa (\) al final de una línea de definición del catálogo continúa el mismo #define en la siguiente línea física. Conserva catalog.h, parse.h, driver.h y read-line.h sin cambios en este laboratorio.

Una estructura agrupa campos relacionados en un registro. Product agrupa un id entero, un array de 32 caracteres para el nombre y un precio entero cents. typedef permite usar Product como nombre corto de ese tipo de registro. Una cadena C termina con el carácter de valor cero \0; por tanto, el array del nombre necesita espacio para el terminador además de los caracteres visibles. Los nombres proporcionados caben; esta tarea no analiza nombres arbitrarios.

Purchase es un segundo tipo de registro. Conserva la posición del producto en el array y la cantidad de esa compra. En basket[line_count].quantity, los corchetes seleccionan una compra y el punto selecciona su campo de cantidad. Las posiciones empiezan en cero. El array global static y line_count se inicializan a cero. sizeof products / sizeof products[0] calcula cuántos registros completos de producto contiene el array.

El bucle proporcionado find_product busca por ID de producto y devuelve su posición en el array, o -1 si no existe. Los dos productos llamados Water tienen distintos IDs y precios: 103 cuesta 75 centavos y 104 es gratuito. El nombre por sí solo no identifica un producto. El catálogo tiene cuatro productos, IDs de 1 a 999 y precios de 0 a 9999 centavos. El mismo programa también debe funcionar al recompilarlo con otro catálogo proporcionado de cuatro filas dentro de esos límites.

Completa approve_purchase comprobando ID, cantidad y capacidad restante antes de escribir en el array. Solo entonces guarda la posición del producto y la cantidad en basket[line_count] e incrementa el contador. La cantidad es de 1 a 20; un precio cero es válido. No combines IDs repetidos: una compra posterior es otra línea. Seis compras al mayor precio y cantidad permitidos suman como máximo 6 × 20 × 9999 = 1,199,880 centavos, dentro de un entero de 32 bits con signo.

puts imprime una cadena terminada y añade un salto de línea. printf usa marcadores de formato, como %s para una cadena terminada en NUL y %d para un entero. print_receipt recorre todas las compras aprobadas. Product product = ... copia ese registro del catálogo a una variable local. Calcula cada subtotal entero, súmalo al total e imprime las filas completas en orden entre el título y la cabecera y un único TOTAL final. Usa los cinco campos separados por barras verticales; pueden variar los espacios alrededor de los campos y las líneas en blanco, pero un recibo con registros adicionales o campos ausentes no está completo.

Sustituye till.c por el código completo siguiente. cat > till.c reemplaza el archivo; <<'EOF' inicia un here-document. El delimitador entre comillas copia literalmente el texto C hasta la línea que contiene solo EOF. Copia todo el bloque, incluida esa última línea.

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 y driver.h proporcionan el bucle de entrada que ya funciona. El controlador pide un ID y una cantidad, y llama a tus funciones de aprobación e impresión. Sus nombres y firmas existentes son puntos de integración; mantenlos como aparecen. Los nombres internos de tipos de registro y variables locales no son el objetivo de la evaluación. En el siguiente laboratorio aprenderás el límite del análisis de entrada.

Compila el programa completo:

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

El controlador envía los avisos y mensajes al error estándar, que sigue visible en el terminal. Solo el recibo terminado va a la salida estándar. Ejecútalo con un nuevo archivo de salida:

./till > receipt.txt

El programa está esperando entrada. Escribe cada valor de la tabla en su indicación, pulsando Enter después de cada uno. No los escribas como comandos del shell. done es el comando exacto de finalización, en minúsculas, y solo funciona en la indicación 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, conservando la compra anterior de 101
done No se pide cantidad Completar el recibo y volver al shell

Sigues viendo las indicaciones y los mensajes Added line porque el controlador los escribe en el error estándar, un flujo separado. Solo el recibo completo va a la salida estándar, que > envía a receipt.txt. Cuando vuelva la indicación del shell, lee el recibo realmente guardado:

cat receipt.txt

Contenido 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

Las dos compras de Oat bar siguen separadas. Suma sus subtotales con Fruit cup: 250 + 230 + 125 = 605 centavos. Un total aparentemente correcto sin todas estas filas perdería los detalles de la transacción del cliente.

Los números usan dígitos decimales, un signo opcional y espacios ASCII o tabuladores opcionales al principio y al final; se permiten ceros iniciales. Los IDs deben existir en el catálogo, las cantidades ser 1..20 y cada línea tener como máximo 63 bytes antes de Enter. Rechazar un ID o una cantidad descarta el candidato pendiente y vuelve a Product ID sin cambiar las compras aprobadas. El lector proporcionado consume una línea demasiado larga en lugar de dividirla en respuestas posteriores. Se aprueban como máximo seis compras. Una cesta vacía seguida de done produce un recibo vacío válido con total 0.00.

Para cancelar en lugar de finalizar, pulsa Ctrl+D en una indicación de entrada vacía. Esa señal de fin de archivo, o EOF durante una línea sin terminar, cancela con estado 2 y sin recibo completo. No exporta ni conserva la transacción inacabada. La redirección ya ha abierto el archivo de salida; usa otro nombre si pruebas una cancelación intencionada. Escribir done como cantidad es texto de cantidad inválido, no una finalización.

Si el recibo solo contiene la última compra, comprueba que guardas en basket[line_count] e incrementas el contador tras cada aprobación. Guarda, recompila y ejecuta una transacción nueva con las mismas compras requeridas. Cada ejecución empieza con una cesta vacía; no vuelve a cargar texto de un recibo antiguo. Conserva till.c, el ejecutable till, los archivos proporcionados y el receipt.txt completo. La comprobación del curso también ejecuta copias intactas del código con detección de límites en tiempo de ejecución en un directorio desechable. Puede detectar errores ejercitados por esas entradas; no demuestra seguridad completa de memoria ni repara tus archivos.

Resumen

Transformaste un ejemplo de un artículo en un recibo completo de varias líneas. Usaste estructuras, un array de compras separadas, IDs estables del catálogo y aritmética en centavos enteros; después guardaste y leíste el resultado real. El siguiente laboratorio se centra en el límite de entrada antes de que una compra pueda entrar en ese array.