MINT lernen

Textaufgaben: Was C++ ergänzt

Ein Anmeldeprogramm für die Robotik-AG, das mit gcc nicht linken will, und ein Schrittwettbewerb, bei dem eine Referenz heimlich Schritte verschwinden lässt.

Dein Fortschritt:
0 / 0 Aufgaben
1

Anmeldung zur Robotik-AG

AFB I–II

Fü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.

C++ · anmeldung.cpp
#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:

Terminal (Auszug)
$ 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:

C++ · Levents Fassung (Anfang)
#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;
  1. Wenden Sie das Programm auf die Eingaben Lina Kowalski 9 und Ben Ott 6 an und notieren Sie jeweils die Ausgabe nach den Eingabeaufforderungen.
  2. Interpretieren Sie Levents Fehlermeldung: Welcher Schritt der Übersetzung scheitert, und warum?
  3. Vergleichen Sie Kims Schreibweise mit using namespace std; und Levents Schreibweise mit std:: 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)
Achten Sie auf 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)
Mögliche Kriterien: Schreibaufwand, Lesbarkeit (sieht man, woher ein Name stammt?), Gefahr von Namenskonflikten mit eigenen Namen, Einsatz in Header-Dateien.

Erwartungshorizont

Erwartungshorizont zu Aufgabe a)
Eingabe Lina Kowalski 9
Login: Lina.Kowalski (13 Zeichen)
Login zu lang, bitte kuerzen.
Teilnahme: 1
Eingabe Ben Ott 6
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)
Kriteriumusing namespace std;std:: vor jedem Namen
Schreibaufwandgering, kurze Namenhöher, jede Zeile länger
Lesbarkeitkompakt, Herkunft nicht sichtbarsofort erkennbar: Name aus der Standardbibliothek
Namenskonfliktemöglich: alle std-Namen sind sichtbarkeine, eigene Namen bleiben getrennt
Header-Dateienmeiden: wirkt in jeder Datei, die den Header einbindetunproblematisch

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.

2

Der Schrittwettbewerb

AFB II–III

Die 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:

C++ · schritte.cpp
#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;
}
Ausgabe
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.“

  1. Untersuchen Sie, wodurch die falsche Schrittzahl in der Ausgabe entsteht, und geben Sie eine korrigierte Fassung von zielErreicht an.
  2. Entwickeln Sie die Funktion void wertung(int schritte, int &punkte, string &abzeichen) und ein main, das sie mit mindestens drei sinnvoll gewählten Schrittzahlen testet.
  3. Erörtern Sie Yusufs These, Referenzen machten Zeiger in C++ überflüssig.

Hinweise

Hinweis zu Aufgabe a)
Vergleichen Sie den Parameter 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)
Die Punkte liefert eine Ganzzahldivision. Beim Abzeichen die Grenzen von oben nach unten prüfen (if … else if …). Wählen Sie Testwerte genau an einer Grenze, z. B. 10 000.
Hinweis zu Aufgabe c)
Sammeln Sie, was Referenzen leichter machen, und prüfen Sie, was nur Zeiger können: Kann eine Referenz „auf nichts“ zeigen oder später auf eine andere Variable umgelenkt werden? Welche Funktionen aus C erwarten Adressen?

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):

C++ · Korrektur
bool zielErreicht(int schritte, int ziel)
{
    return schritte >= ziel;
}

Dann lautet die Ausgabe Ziel erreicht! und Heute: 12500 Schritte.

Erwartungshorizont zu Aufgabe b)
C++ · wertung.cpp
#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;
}
Ausgabe (g++ -Wall wertung.cpp -o wertung)
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.