Die Mensakarte
AFB I–IIAn der Schule von Mo wird das Mittagessen mit einer aufladbaren Karte bezahlt. Die Informatik-AG bildet die Karte in C++ als Klasse nach; das Guthaben wird in Cent gespeichert, damit keine Rundungsfehler entstehen.
#ifndef MENSAKARTE_H #define MENSAKARTE_H class Mensakarte { private: int guthaben; // in Cent public: Mensakarte(int start) { guthaben = start; } void aufladen(int betrag) { if (betrag > 0) { guthaben = guthaben + betrag; } } bool bezahlen(int preis) { if (preis <= guthaben) { guthaben = guthaben - preis; return true; } return false; } int getGuthaben() { return guthaben; } }; #endif
In einem Testprogramm (mit #include <iostream>, #include "mensakarte.h" und using namespace std;) stehen in main nacheinander die folgenden fünf Anweisungen:
Mensakarte k(500); // (1) k.guthaben = 10000; // (2) Mensakarte m; // (3) k.aufladen(-200); // (4) cout << k.getGuthaben; // (5)
Mo behauptet, nach diesen Anweisungen habe er genau 1,40 € auf seiner Karte:
Mensakarte mo(1000); mo.bezahlen(420); mo.bezahlen(650); mo.aufladen(-300); mo.bezahlen(440);
- Erstellen Sie ein UML-Klassendiagramm der Klasse
Mensakarte. - Ordnen Sie jede der Anweisungen (1) bis (5) einer der Kategorien „wird übersetzt“ oder „Übersetzungsfehler“ zu.
- Bestätigen Sie Mos Behauptung über sein Guthaben.
Hinweise
Hinweis zu Aufgabe a)
- steht für private, + für public; der Typ steht nach einem Doppelpunkt hinter dem Namen.Hinweis zu Aufgabe b)
Hinweis zu Aufgabe c)
guthaben Anweisung für Anweisung. Achtung: Nicht jeder Aufruf verändert das Guthaben.Erwartungshorizont
Erwartungshorizont zu Aufgabe a)
Bewertet werden: drei Felder, Sichtbarkeiten -/+, Parameter- und Rückgabetypen, Konstruktor ohne Rückgabetyp.
Erwartungshorizont zu Aufgabe b)
| Nr. | Kategorie | Begründung |
|---|---|---|
| (1) | wird übersetzt | Konstruktor mit einem int vorhanden |
| (2) | Fehler | guthaben ist private — g++: „is private within this context“ |
| (3) | Fehler | kein Konstruktor ohne Parameter — „no matching function for call to Mensakarte::Mensakarte()“ |
| (4) | wird übersetzt | öffentliche Methode; sie ändert wegen betrag > 0 aber nichts |
| (5) | Fehler | Klammern fehlen: k.getGuthaben() — „invalid use of non-static member function“ |
Erwartungshorizont zu Aufgabe c)
| Anweisung | Rückgabe | guthaben (Cent) |
|---|---|---|
Mensakarte mo(1000); | — | 1000 |
mo.bezahlen(420); | true | 580 |
mo.bezahlen(650); | false (650 > 580) | 580 |
mo.aufladen(-300); | — | 580 (negativ, ignoriert) |
mo.bezahlen(440); | true | 140 |
Am Ende stehen 140 Cent = 1,40 € auf der Karte — Mos Behauptung stimmt. Mit einem Testprogramm und getGuthaben() nach jeder Zeile ergibt sich dieselbe Folge 580, 580, 580, 140.
Ein Parkhaus mit Schranke
AFB II–IIIIm Projekt „Smarte Stadt“ bauen Jana und Luis ein Modell-Parkhaus mit Schranke und Anzeigetafel. Die Software soll die Zahl der belegten Plätze verwalten. Sie einigen sich auf das folgende Klassendiagramm:
Vereinbart ist: Ein neues Parkhaus ist leer. einfahren() gibt false zurück und ändert nichts, wenn das Parkhaus voll ist; sonst wird ein Platz belegt und true zurückgegeben. ausfahren() darf die Belegung nie unter 0 senken.
Luis meint: „Das private: können wir uns sparen und einfach struct schreiben. belegt wird ja sowieso nur über einfahren() und ausfahren() geändert, also kann nichts schiefgehen.“
Jana schlägt dagegen vor, statt einfahren() und ausfahren() einfach eine öffentliche Methode setBelegt(int anzahl) anzubieten, „dann ist die Klasse kürzer und man kann alles einstellen“.
- Implementieren Sie die Klasse nach dem Klassendiagramm, aufgeteilt in
parkhaus.hundparkhaus.cpp, sowie ein kurzesmainzum Testen. Geben Sie auch den Befehl zum Übersetzen an. - Widerlegen Sie Luis’ Aussage.
- Bewerten Sie Janas Vorschlag,
einfahren()undausfahren()durchsetBelegt(int anzahl)zu ersetzen.
Hinweise
Hinweis zu Aufgabe a)
.cpp-Datei beginnt jede Methode mit Parkhaus::. einfahren() kann istVoll() benutzen.Hinweis zu Aufgabe b)
class und struct in C++? Überlegen Sie, wer außer den eigenen Methoden noch auf belegt zugreifen könnte, wenn es öffentlich ist. Ein einziges Gegenbeispiel genügt.Hinweis zu Aufgabe c)
Erwartungshorizont
Erwartungshorizont zu Aufgabe a)
#ifndef PARKHAUS_H #define PARKHAUS_H class Parkhaus { private: int kapazitaet; int belegt; public: Parkhaus(int kap); bool einfahren(); void ausfahren(); int freiePlaetze(); bool istVoll(); }; #endif
#include "parkhaus.h" Parkhaus::Parkhaus(int kap) : kapazitaet(kap), belegt(0) { } bool Parkhaus::einfahren() { if (istVoll()) { return false; // Schranke bleibt zu } belegt = belegt + 1; return true; } void Parkhaus::ausfahren() { if (belegt > 0) { belegt = belegt - 1; } } int Parkhaus::freiePlaetze() { return kapazitaet - belegt; } bool Parkhaus::istVoll() { return belegt >= kapazitaet; }
#include <iostream> #include "parkhaus.h" using namespace std; int main() { Parkhaus p(3); for (int i = 1; i <= 4; i++) { if (p.einfahren()) { cout << "Auto " << i << ": Schranke auf" << endl; } else { cout << "Auto " << i << ": besetzt" << endl; } } p.ausfahren(); cout << "Frei: " << p.freiePlaetze() << endl; p.ausfahren(); p.ausfahren(); p.ausfahren(); // leer: belegt bleibt 0 cout << "Frei: " << p.freiePlaetze() << endl; return 0; }
$ g++ main.cpp parkhaus.cpp -o parkhaus $ ./parkhaus Auto 1: Schranke auf Auto 2: Schranke auf Auto 3: Schranke auf Auto 4: besetzt Frei: 1 Frei: 3
Bewertet werden: Include-Guard, private Attribute, Konstruktor setzt belegt auf 0, Klasse::methode-Schreibweise, beide Grenzfälle (voll, leer) abgefangen, Test mit mehr Autos als Plätzen. Statt der Initialisierungsliste ist auch eine Zuweisung im Konstruktorrumpf richtig.
Erwartungshorizont zu Aufgabe b)
Bei struct sind alle Mitglieder standardmäßig public. Dann kann jeder Programmteil belegt direkt ändern — an einfahren() und ausfahren() und ihren Prüfungen vorbei. Gegenbeispiel:
#include <iostream> using namespace std; struct Parkhaus { int kapazitaet; int belegt; Parkhaus(int kap) : kapazitaet(kap), belegt(0) {} int freiePlaetze() { return kapazitaet - belegt; } }; int main() { Parkhaus p(120); p.belegt = 150; cout << "Frei: " << p.freiePlaetze() << endl; return 0; }
Frei: -30
Das Programm wird fehlerfrei übersetzt, die Anzeigetafel zeigt aber −30 freie Plätze. Dass belegt „nur über die Methoden“ geändert wird, ist also eine Absprache, keine Garantie. Erst private lässt der Compiler solche Zugriffe nicht mehr zu (Kapselung).
Erwartungshorizont zu Aufgabe c)
Kürze: Die Klasse wird tatsächlich etwas kürzer, und für Sonderfälle (z. B. Zählung nach einem Stromausfall neu setzen) ist ein Setter praktisch.
Gültigkeit: Ohne Prüfung erlaubt setBelegt Werte wie −5 oder 500 — dasselbe Problem wie bei Luis’ struct. Mit Prüfung im Setter lässt sich das zwar verhindern, die Regel „nur, wenn nicht voll“ steckt aber dann im Aufrufer.
Verständlichkeit: p.einfahren() beschreibt das Ereignis an der Schranke; mit dem Setter müsste jeder Aufrufer selbst p.setBelegt(p.getBelegt() + 1) rechnen (dazu bräuchte es noch einen Getter) und vorher prüfen — mehr Code an vielen Stellen, mehr Fehlerquellen. Außerdem liefert einfahren() gleich die Antwort für die Schranke.
Urteil: Als Ersatz ist der Vorschlag schlechter, weil er die Kapselung schwächt und Logik aus der Klasse herauszieht. Vertretbar wäre ein zusätzlicher Setter mit Prüfung für Sonderfälle. Eine vollständige Antwort nennt mindestens zwei Kriterien und kommt zu einem begründeten Urteil.
