Anmeldung zur Robotik-AG
AFB I–IIFür die Anmeldung zur Robotik-AG schreibt Kim ein kleines C++-Programm. Es erzeugt aus Vor- und Nachnamen einen Login für die AG-Rechner und prüft, ob die Person aus Jahrgang 7 oder höher kommt.
#include <iostream> #include <string> using namespace std; int main() { string vorname, nachname; int jahrgang; cout << "Vorname: "; cin >> vorname; cout << "Nachname: "; cin >> nachname; cout << "Jahrgang: "; cin >> jahrgang; string login = vorname + "." + nachname; bool darfTeilnehmen = jahrgang >= 7; cout << "Login: " << login << " (" << login.length() << " Zeichen)" << endl; if (login.length() > 12) { cout << "Login zu lang, bitte kuerzen." << endl; } cout << "Teilnahme: " << darfTeilnehmen << endl; return 0; }
Ihr Freund Levent übersetzt das Programm aus Gewohnheit mit gcc statt mit g++ und erhält viele Zeilen wie diese:
$ gcc anmeldung.cpp -o anmeldung /usr/bin/ld: /tmp/ccaCvnb9.o: in function `main': /usr/bin/ld: anmeldung.cpp:(.text+0x47): undefined reference to `std::cout' /usr/bin/ld: anmeldung.cpp:(.text+0x60): undefined reference to `std::cin' … collect2: error: ld returned 1 exit status
Außerdem hat Levent im Internet gelesen, man solle using namespace std; lieber weglassen. Seine Fassung beginnt so:
#include <iostream> #include <string> int main() { std::string vorname, nachname; int jahrgang; std::cout << "Vorname: "; std::cin >> vorname; // ... usw. std::cout << "Login: " << login << std::endl;
- Wenden Sie das Programm auf die Eingaben
Lina Kowalski 9undBen Ott 6an und notieren Sie jeweils die Ausgabe nach den Eingabeaufforderungen. - Interpretieren Sie Levents Fehlermeldung: Welcher Schritt der Übersetzung scheitert, und warum?
- Vergleichen Sie Kims Schreibweise mit
using namespace std;und Levents Schreibweise mitstd::nach mindestens drei Kriterien.
Hinweise
Hinweis zu Aufgabe a)
+ hängt string-Werte aneinander, .length() zählt alle Zeichen, auch den Punkt. Ein bool wird von cout als Zahl ausgegeben.Hinweis zu Aufgabe b)
ld und undefined reference. Welches Werkzeug hieß in 2.1.1 „Linker“, und was fügt es zum eigenen Code hinzu?Hinweis zu Aufgabe c)
Erwartungshorizont
Erwartungshorizont zu Aufgabe a)
Login: Lina.Kowalski (13 Zeichen) Login zu lang, bitte kuerzen. Teilnahme: 1
Login: Ben.Ott (7 Zeichen) Teilnahme: 0
Lina.Kowalski hat 4 + 1 + 8 = 13 Zeichen, also mehr als 12. true erscheint als 1, false als 0.
Erwartungshorizont zu Aufgabe b)
Die Meldungen kommen von ld, dem Linker; die letzte Zeile bestätigt „ld returned 1 exit status“. Präprozessor und Compiler haben also funktioniert — gcc erkennt an der Endung .cpp, dass es C++ ist, und übersetzt den Code.
„undefined reference to std::cout“ bedeutet: Der Code benutzt std::cout und std::cin, aber deren Definitionen fehlen beim Zusammenfügen. Sie liegen in der C++-Standardbibliothek, die gcc — anders als g++ — nicht automatisch dazulinkt. Lösung: g++ anmeldung.cpp -o anmeldung.
Erwartungshorizont zu Aufgabe c)
| Kriterium | using namespace std; | std:: vor jedem Namen |
|---|---|---|
| Schreibaufwand | gering, kurze Namen | höher, jede Zeile länger |
| Lesbarkeit | kompakt, Herkunft nicht sichtbar | sofort erkennbar: Name aus der Standardbibliothek |
| Namenskonflikte | möglich: alle std-Namen sind sichtbar | keine, eigene Namen bleiben getrennt |
| Header-Dateien | meiden: wirkt in jeder Datei, die den Header einbindet | unproblematisch |
Beispiel für einen Konflikt: Eine eigene globale Variable int count kollidiert mit std::count aus <algorithm>; g++ meldet dann „reference to 'count' is ambiguous“.
Gemeinsam: Beide Fassungen erzeugen dasselbe Programm mit derselben Ausgabe. Fazit: In kleinen .cpp-Übungsprogrammen ist using namespace std; vertretbar, in Headern und größeren Projekten ist std:: sicherer.
Der Schrittwettbewerb
AFB II–IIIDie Klasse 9c veranstaltet in der Projektwoche einen Schrittwettbewerb: Jeden Abend werden die Schritte eingetragen, ab 10 000 Schritten gilt das Tagesziel als erreicht. Yusuf schreibt dafür eine Hilfsfunktion und testet sie:
#include <iostream> using namespace std; bool zielErreicht(int &schritte, int ziel) { schritte = schritte - ziel; // Rest berechnen return schritte >= 0; } int main() { int schritte = 12500; if (zielErreicht(schritte, 10000)) { cout << "Ziel erreicht!" << endl; } cout << "Heute: " << schritte << " Schritte" << endl; return 0; }
Ziel erreicht! Heute: 2500 Schritte
Später soll eine Funktion wertung für jeden Tag Punkte und ein Abzeichen vergeben: 1 Punkt pro volle 1 000 Schritte; „Gold“ ab 15 000, „Silber“ ab 10 000, „Bronze“ ab 5 000 Schritten, sonst „keins“. Die Funktion soll beides an den Aufrufer zurückliefern.
Yusuf findet Referenzen so praktisch, dass er behauptet: „Seit es Referenzen gibt, braucht man in C++ eigentlich keine Zeiger mehr.“
- Untersuchen Sie, wodurch die falsche Schrittzahl in der Ausgabe entsteht, und geben Sie eine korrigierte Fassung von
zielErreichtan. - Entwickeln Sie die Funktion
void wertung(int schritte, int &punkte, string &abzeichen)und einmain, das sie mit mindestens drei sinnvoll gewählten Schrittzahlen testet. - Erörtern Sie Yusufs These, Referenzen machten Zeiger in C++ überflüssig.
Hinweise
Hinweis zu Aufgabe a)
int &schritte mit einem gewöhnlichen int-Parameter. Was passiert in main, wenn die Funktion ihren Parameter verändert? Muss sie ihn überhaupt verändern?Hinweis zu Aufgabe b)
if … else if …). Wählen Sie Testwerte genau an einer Grenze, z. B. 10 000.Hinweis zu Aufgabe c)
Erwartungshorizont
Erwartungshorizont zu Aufgabe a)
schritte ist ein Referenzparameter, also nur ein zweiter Name für die Variable schritte aus main. Die Zeile schritte = schritte - ziel; überschreibt deshalb den Wert in main: 12 500 − 10 000 = 2 500. Die Funktion hat eine unerwünschte Nebenwirkung.
Eine Prüffunktion soll nichts verändern; es genügt ein Wertparameter (oder gar keine Zwischenrechnung):
bool zielErreicht(int schritte, int ziel) { return schritte >= ziel; }
Dann lautet die Ausgabe Ziel erreicht! und Heute: 12500 Schritte.
Erwartungshorizont zu Aufgabe b)
#include <iostream> #include <string> using namespace std; void wertung(int schritte, int &punkte, string &abzeichen) { punkte = schritte / 1000; if (schritte >= 15000) { abzeichen = "Gold"; } else if (schritte >= 10000) { abzeichen = "Silber"; } else if (schritte >= 5000) { abzeichen = "Bronze"; } else { abzeichen = "keins"; } } int main() { int tests[4] = {16200, 10000, 7999, 4300}; for (int i = 0; i < 4; i++) { int punkte = 0; string abzeichen; wertung(tests[i], punkte, abzeichen); cout << tests[i] << " Schritte: " << punkte << " Punkte, Abzeichen " << abzeichen << endl; } return 0; }
16200 Schritte: 16 Punkte, Abzeichen Gold 10000 Schritte: 10 Punkte, Abzeichen Silber 7999 Schritte: 7 Punkte, Abzeichen Bronze 4300 Schritte: 4 Punkte, Abzeichen keins
Bewertet werden: Wertparameter für schritte, Referenzen für die beiden Ergebnisse, Ganzzahldivision, richtige Reihenfolge der Grenzen und Testfälle an einer Grenze (hier 10 000) und unter 5 000.
Erwartungshorizont zu Aufgabe c)
Für die These: Für Ausgabeparameter wie in wertung sind Referenzen einfacher — kein & beim Aufruf, kein * im Rumpf, weniger Fehlerquellen. Eine Referenz muss bei der Deklaration an eine Variable gebunden werden und kann daher nicht versehentlich ungültig (NULL) sein.
Gegen die These: Eine Referenz bleibt für immer an dieselbe Variable gebunden; ein Zeiger kann umgelenkt werden (wie z = &tempo; in 2.2.2). Ein Zeiger kann bewusst „auf nichts“ zeigen (NULL/nullptr) — das braucht man z. B. für „noch kein Wert vorhanden“. Arrays werden als Adresse ihres ersten Elements übergeben, und C-Funktionen wie scanf erwarten Zeiger; da C-Code auch in C++ läuft, begegnen sie einem weiterhin.
Abwägung: Referenzen ersetzen Zeiger in vielen Alltagsfällen, aber nicht in allen. Die These ist daher zu pauschal; treffend wäre: „Wo eine Referenz reicht, nimmt man in C++ die Referenz.“ Eine vollständige Antwort stellt mindestens zwei Argumente je Seite gegenüber und formuliert ein begründetes Fazit.
