Einführung
Sunny Snacks braucht einen Kassenbon, der jeden Kauf bewahrt, auch wenn ein Kunde denselben Snack erneut kauft. Du führst zunächst ein Beispiel mit einem Artikel aus und vervollständigst dann ein C-Programm, das Käufe getrennt speichert und jede Zeile mit Einzelpreis, Zwischensumme und vollständiger Gesamtsumme ausgibt.
Du solltest Variablen, Bedingungen, Schleifen, Arrays und Funktionen in C sowie grundlegende Terminaleingaben und Pfade kennen. Dieses Lab führt die benötigten Strukturen und Details der Textausgabe ein. Die Browserumgebung enthält bereits einen Compiler und das Terminal Workspace. Die Beträge sind synthetische ganzzahlige Centwerte, kein Zahlungs- oder Buchhaltungssystem. Die Lese- und Übungszeit ist eine Schätzung des Autors; sie wurde nicht mit Anfängern gemessen.
Einen Kassenbon mit einem Artikel lesen
In diesem Schritt führst du ein kleines vollständiges Beispiel aus und änderst einen Kauf von einem Artikel in einen Kauf von zwei Artikeln.
Öffne den Tab Workspace. Das Terminal startet in /home/labex/project/receipts. cd wechselt das Verzeichnis; pwd zeigt das aktuelle an und ls listet Dateien auf. Diese Befehle helfen auch bei einem falsch geschriebenen Pfad oder Namen.
cd /home/labex/project/receipts
pwd
ls
sample.c ist ein vollständiges Beispiel mit einem Artikel. till.c ist das unvollständige Programm für mehrere Artikel im nächsten Schritt. Katalog, Parser und Eingabetreiber sind vorbereitete Dateien; ändere sie nicht. cat zeigt eine Textdatei an, ohne sie zu bearbeiten:
cat sample.c
Die Variable quantity des Beispiels beginnt mit 1. unit_cents ist 125: eine Währungseinheit und 25 Cent. Die Multiplikation berechnet eine Zwischensumme in Cent. Behalte diese Einheit während der gesamten Berechnung bei. Ganzzahldivision durch 100 ergibt den ganzzahligen Anteil; % 100 liefert den Rest. In printf gibt %d eine Ganzzahl aus, %s eine abgeschlossene C-Zeichenkette, %02d füllt den Nachkommateil auf zwei Stellen auf und \n beendet die Zeile. So erscheinen 250 Cent als 2.50, ohne Gleitkommaarithmetik.
Kompiliere das Programm. gcc ist der C-Compiler; -std=c11 wählt C11, -Wall -Wextra aktiviert Warnungen und -o sample benennt die ausführbare Datei. ./sample führt diese Datei aus dem aktuellen Verzeichnis aus.
gcc -std=c11 -Wall -Wextra sample.c -o sample
./sample
Das vollständige normale Ergebnis lautet:
SUNNY SNACKS
ID | ITEM | QTY | UNIT | SUBTOTAL
101 | Oat bar | 1 | 1.25 | 1.25
TOTAL | 1.25
ID bedeutet Produktkennung: 101 gehört zu diesem Katalogeintrag. UNIT ist der Preis eines Artikels; SUBTOTAL ist die Menge dieses Kaufs multipliziert mit seinem Einzelpreis. Die senkrechten Striche trennen lesbare Spalten. Der abschließende TOTAL addiert die Zwischensummen aller Käufe; hier gibt es nur einen.
Ändere die Menge auf 2. Der folgende sed-Befehl ersetzt den exakten alten Text durch den neuen; -i speichert die Änderung. Die einfachen Anführungszeichen halten die Ersetzung in einem einzigen Shellargument zusammen.
sed -i 's/int quantity = 1;/int quantity = 2;/' sample.c
Kompiliere nach der Bearbeitung neu: Eine Änderung am Quellcode aktualisiert eine alte ausführbare Datei nicht automatisch. Der Shelloperator > leitet die Standardausgabe in eine Datei um. Er öffnet oder leert sie vor dem Start des C-Programms; verwende daher den angegebenen neuen Ausgabenamen.
gcc -std=c11 -Wall -Wextra sample.c -o sample
./sample > sample-receipt.txt
cat sample-receipt.txt
Die Menge beträgt jetzt 2, Zwischensumme und Gesamtsumme jeweils 2.50; der Einzelpreis bleibt 1.25. Prüfe von Hand: 125 × 2 = 250 Cent. Bewahre sample.c, die ausführbare Datei sample und sample-receipt.txt.
Fehlt eine Datei, kehre mit dem gelernten cd zurück und prüfe ihren Namen mit ls. Meldet der Compiler einen Fehler, korrigiere die angegebene Zeile und kompiliere vor dem Ausführen neu; eine alte ausführbare Datei kann nach einer fehlgeschlagenen Kompilierung noch existieren.
Jeden Kauf bewahren und ausgeben
In diesem Schritt bewahrst du Käufe als getrennte Datensätze und erzeugst einen vollständigen Kassenbon mit drei Kaufzeilen.
Beginne in Workspace unter /home/labex/project/receipts. Lies den Katalog und das unvollständige Programm:
cd /home/labex/project/receipts
cat catalog.h
cat till.c
Die Headerdatei catalog.h stellt den Namen des Standes, die Höchstzahl von sechs Käufen, die Höchstmenge 20 und vier Katalogeinträge bereit. #include "catalog.h" bindet diese Definitionen beim Kompilieren ein. #define benennt eine Konstante oder einen Initialisierer; CATALOG_ROWS liefert die Zeilen zur Initialisierung des Produktarrays. Ein Rückstrich (\) am Ende einer Definitionszeile des Katalogs setzt dasselbe #define in der nächsten physischen Zeile fort. Bewahre catalog.h, parse.h, driver.h und read-line.h in diesem Lab unverändert.
Eine Struktur fasst zusammengehörige Felder in einem Datensatz zusammen. Product enthält eine ganzzahlige id, ein Array mit 32 Zeichen für den Namen und einen ganzzahligen Preis cents. Mit typedef lässt sich Product als kurzer Name für diesen Datensatztyp verwenden. Eine C-Zeichenkette endet mit dem Zeichen mit dem Wert null \0; das Namensarray braucht daher neben den sichtbaren Zeichen Platz für das Abschlusszeichen. Die bereitgestellten Namen passen hinein; beliebige Namen zu verarbeiten gehört nicht zu dieser Aufgabe.
Purchase ist ein zweiter Datensatztyp. Er bewahrt die Position des Produkts im Array und die Menge dieses Kaufs. In basket[line_count].quantity wählen die eckigen Klammern einen Kauf aus und der Punkt sein Mengenfeld. Positionen beginnen bei null. Das globale static-Array und line_count werden mit null initialisiert. sizeof products / sizeof products[0] berechnet die Anzahl vollständiger Produktdatensätze im Array.
Die bereitgestellte Schleife find_product sucht anhand der Produkt-ID und liefert die Arrayposition oder -1, falls es das Produkt nicht gibt. Die beiden Produkte namens Water haben unterschiedliche IDs und Preise: 103 kostet 75 Cent und 104 ist kostenlos. Der Name allein identifiziert ein Produkt nicht. Der Katalog hat vier Produkte, IDs von 1 bis 999 und Preise von 0 bis 9999 Cent. Dasselbe Programm muss auch nach dem Neukompilieren mit einem anderen bereitgestellten Katalog mit vier Zeilen innerhalb dieser Grenzen funktionieren.
Vervollständige approve_purchase, indem du ID, Menge und freie Kapazität vor dem Schreiben ins Array prüfst. Speichere erst danach Produktposition und Menge in basket[line_count] und erhöhe den Zähler. Die Menge liegt zwischen 1 und 20; ein Preis von null ist gültig. Fasse wiederholte IDs nicht zusammen: Ein späterer Kauf ist eine weitere Zeile. Sechs Käufe zum höchsten erlaubten Preis und mit der höchsten erlaubten Menge ergeben höchstens 6 × 20 × 9999 = 1,199,880 Cent; das passt in eine vorzeichenbehaftete 32-Bit-Ganzzahl.
puts gibt eine abgeschlossene Zeichenkette aus und fügt einen Zeilenumbruch hinzu. printf verwendet Formatplatzhalter wie %s für eine NUL-terminierte Zeichenkette und %d für eine Ganzzahl. print_receipt durchläuft alle genehmigten Käufe. Product product = ... kopiert diesen Katalogdatensatz in eine lokale Variable. Berechne jede ganzzahlige Zwischensumme, addiere sie zur Gesamtsumme und gib die vollständigen Zeilen der Reihe nach zwischen Titel und Spaltenkopf sowie einem einzigen abschließenden TOTAL aus. Verwende die fünf durch senkrechte Striche getrennten Felder; Leerraum um Felder und Leerzeilen dürfen variieren, aber zusätzliche Datensätze oder fehlende Felder ergeben keinen vollständigen Bon.
Ersetze till.c durch den folgenden vollständigen Code. cat > till.c ersetzt die Datei; <<'EOF' beginnt ein Here-Dokument. Das in Anführungszeichen gesetzte Begrenzungswort kopiert den C-Text wörtlich bis zur Zeile, die nur EOF enthält. Kopiere den ganzen Block einschließlich dieser letzten Zeile.
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 und driver.h liefern die bereits funktionierende Eingabeschleife. Der Treiber fragt nach ID und Menge und ruft deine Genehmigungs- und Ausgabefunktionen auf. Ihre vorhandenen Namen und Signaturen sind Integrationspunkte; behalte sie wie gezeigt bei. Die internen Namen von Datensatztypen und lokalen Variablen sind nicht das Bewertungsziel. Im nächsten Lab lernst du die Grenze der Eingabeprüfung kennen.
Kompiliere das vollständige Programm:
gcc -std=c11 -Wall -Wextra till.c -o till
Der Treiber sendet Eingabeaufforderungen und Meldungen an die Standardfehlerausgabe, die im Terminal sichtbar bleibt. Nur der fertige Kassenbon geht an die Standardausgabe. Starte das Programm mit einer neuen Ausgabedatei:
./till > receipt.txt
Das Programm wartet auf Eingaben. Gib jeden Tabellenwert an seiner Eingabeaufforderung ein und drücke danach Enter. Gib die Werte nicht als Shellbefehle ein. done ist der exakte Abschlussbefehl in Kleinbuchstaben und funktioniert nur bei Product ID.
| Eingabe bei Product ID | Eingabe bei Quantity | Ergebnis |
|---|---|---|
101 |
2 |
Added line 1 |
102 |
1 |
Added line 2 |
101 |
1 |
Added line 3, der frühere Kauf von 101 bleibt erhalten |
done |
Keine Menge abgefragt | Kassenbon abschließen und zur Shell zurückkehren |
Du siehst weiterhin die Aufforderungen und Added line-Meldungen, weil der Treiber sie in die Standardfehlerausgabe, einen getrennten Datenstrom, schreibt. Nur der vollständige Kassenbon geht an die Standardausgabe, die > nach receipt.txt leitet. Lies den tatsächlich gespeicherten Bon, sobald die Shellaufforderung zurückkehrt:
cat receipt.txt
Erwarteter Inhalt:
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
Die beiden Käufe von Oat bar bleiben getrennt. Addiere ihre Zwischensummen mit Fruit cup: 250 + 230 + 125 = 605 Cent. Eine scheinbar richtige Gesamtsumme ohne all diese Zeilen würde die Transaktionsdetails des Kunden verlieren.
Zahlen verwenden Dezimalziffern, ein optionales Vorzeichen und optionale ASCII-Leerzeichen oder Tabulatoren am Anfang und Ende; führende Nullen sind erlaubt. IDs müssen im Katalog existieren, Mengen in 1..20 liegen und jede Zeile darf vor Enter höchstens 63 Bytes haben. Die Ablehnung einer ID oder Menge verwirft den ausstehenden Kandidaten und kehrt zu Product ID zurück, ohne genehmigte Käufe zu ändern. Der bereitgestellte Leser verbraucht eine überlange Zeile vollständig, statt sie in spätere Antworten aufzuteilen. Höchstens sechs Käufe werden genehmigt. Ein leerer Warenkorb gefolgt von done erzeugt einen gültigen leeren Bon mit der Gesamtsumme 0.00.
Um abzubrechen statt abzuschließen, drücke Ctrl+D an einer leeren Eingabeaufforderung. Dieses Dateiende-Signal oder EOF während einer unvollständigen Zeile bricht mit Status 2 und ohne vollständigen Bon ab. Es exportiert oder bewahrt die unfertige Transaktion nicht. Die Umleitung hat die Ausgabedatei bereits geöffnet; verwende einen anderen Namen, wenn du einen absichtlichen Abbruch testest. done als Menge ist ungültiger Mengentext und kein Abschluss.
Enthält der Bon nur den letzten Kauf, prüfe, ob du in basket[line_count] speicherst und den Zähler nach jeder Genehmigung erhöhst. Speichere, kompiliere neu und starte eine neue Transaktion mit denselben erforderlichen Käufen. Jeder Programmlauf beginnt mit einem leeren Warenkorb; er lädt keinen Text aus einem alten Bon. Bewahre till.c, die ausführbare Datei till, die bereitgestellten Dateien und die vollständige receipt.txt. Die Kursprüfung führt außerdem unveränderte Codekopien mit einer Laufzeitprüfung auf Grenzüberschreitungen in einem Wegwerfverzeichnis aus. Sie kann Fehler erkennen, die durch diese Eingaben ausgeführt werden; sie beweist keine vollständige Speichersicherheit und repariert deine Dateien nicht.
Zusammenfassung
Du hast aus einem Beispiel mit einem Artikel einen vollständigen mehrzeiligen Kassenbon erstellt. Dazu hast du Strukturen, ein Array getrennter Käufe, stabile Katalog-IDs und Berechnungen in ganzzahligen Centwerten verwendet und anschließend das tatsächliche Ergebnis gespeichert und gelesen. Im nächsten Lab geht es um die Eingabeprüfung, bevor ein Kauf in dieses Array aufgenommen werden darf.


