MINT lernen

Textaufgaben: Zeiger

Ein Fahrradcomputer, der Werte über Zeiger verändert, und ein Tischtennis-Zähler, dessen Punkte einfach nicht steigen wollen.

Dein Fortschritt:
0 / 0 Aufgaben
1

Ein Fahrradcomputer im Test

AFB I–II

Jonas baut für sein Fahrrad einen kleinen Fahrradcomputer. Bevor er Sensoren anschließt, testet er auf dem PC, wie Werte über einen Zeiger verändert werden. Das Programm speichert die gefahrene Strecke in Kilometern und die Durchschnittsgeschwindigkeit in km/h.

Die Skizze zeigt den Speicher vereinfacht direkt nach Anweisung (1). Die Adressen sind ausgedacht; beim echten Programm ändern sie sich bei jedem Start.

C · fahrrad.c
#include <stdio.h>

int main(void)
{
    int strecke = 12;        // gefahrene Kilometer
    int tempo = 18;          // Durchschnitt in km/h
    int *z = &strecke;       // (1)
    *z = *z + 5;             // (2)
    z = &tempo;              // (3)
    *z = 2 * *z;             // (4)
    printf("%d %d %d\n", strecke, tempo, *z);
    return 0;
}
Speicher nach Anweisung (1)
12streckeAdr. 0x2000int · 4 Byte18tempoAdr. 0x2004int · 4 Byte0x2000zAdr. 0x2008int * · 8 Bytez zeigt auf strecke
Vereinfachte Adressen; ein int belegt hier typisch 4 Byte, ein Zeiger 8 Byte.
  1. Beschreiben Sie, was die Anweisungen (1) bis (4) jeweils bewirken.
  2. Bestimmen Sie die Ausgabe des Programms sowie die Adresse, die am Ende in z steht (Adressen wie in der Skizze).
  3. Erklären Sie, warum Jonas’ spätere Ergänzung int umfang; scanf("%d", umfang); für den Radumfang fehlerhaft ist, und wie die Zeile richtig lautet.

Hinweise

Hinweis zu Aufgabe a)
Unterscheiden Sie genau: Steht vor z ein Stern oder nicht? Ohne Stern ist die Adresse gemeint, mit Stern der Wert an dieser Adresse. In der Deklaration (1) gehört der Stern dagegen zum Typ.„Beschreiben“ heißt: jede Anweisung in eigenen Worten mit Fachbegriffen wiedergeben, noch ohne Endergebnis.
Hinweis zu Aufgabe b)
Führen Sie eine kleine Tabelle mit den Spalten strecke, tempo und z und tragen Sie nach jeder Anweisung die Werte ein.
Hinweis zu Aufgabe c)
Was braucht scanf, um einen Wert in eine Variable von main schreiben zu können — den bisherigen Inhalt oder den Ort der Variablen? Vergleichen Sie mit *z = … aus Anweisung (2).

Erwartungshorizont

Erwartungshorizont zu Aufgabe a)

(1) Es wird eine Zeigervariable z vom Typ int * angelegt und mit der Adresse von strecke initialisiert — z zeigt auf strecke.

(2) *z dereferenziert den Zeiger: Der Wert an der Adresse in z (also strecke) wird gelesen, um 5 erhöht und dorthin zurückgeschrieben.

(3) Ohne Stern wird der Zeiger selbst verändert: z erhält die Adresse von tempo und zeigt ab jetzt auf tempo. strecke bleibt unverändert.

(4) Der Wert, auf den z zeigt (tempo), wird verdoppelt und über den Zeiger zurückgeschrieben.

Erwartungshorizont zu Aufgabe b)
nachstrecketempoz
(1)12180x2000
(2)17180x2000
(3)17180x2004
(4)17360x2004

Ausgabe: 17 36 36 — *z liest am Ende den Wert von tempo. In z steht die Adresse 0x2004 (die Adresse von tempo).

Erwartungshorizont zu Aufgabe c)

Parameter werden in C als Kopie übergeben. Mit scanf("%d", umfang) erhielte scanf nur den (noch unbestimmten) Wert von umfang und würde ihn als Adresse benutzen — das Programm schreibt an eine zufällige Stelle im Speicher oder stürzt ab.

Richtig: scanf("%d", &umfang); — scanf bekommt die Adresse, also einen Zeiger vom Typ int *, und schreibt über ihn wie in Anweisung (2) in die Variable.

gcc meldet mit -Wall: format '%d' expects argument of type 'int *', but argument 2 has type 'int'.

2

Der Tischtennis-Zähler

AFB II–III

Lara und Emir programmieren für die Tischtennis-AG einen Zähler, der später auf einem Tablet an der Platte laufen soll. Es gilt: Einen Satz gewinnt, wer zuerst 11 Punkte hat und dabei mindestens 2 Punkte vorn liegt. Bei 10 : 10 geht es also weiter, bis jemand 2 Punkte Vorsprung hat, z. B. 12 : 10 oder 15 : 13.

Ihr erster Versuch, einen Punkt zu zählen, liefert nach drei Punkten immer noch den Stand 0 : 0:

C · zaehler.c (erster Versuch)
#include <stdio.h>

void punkt(int stand)
{
    stand = stand + 1;
}

int main(void)
{
    int lara = 0;
    int emir = 0;
    punkt(lara);
    punkt(lara);
    punkt(emir);
    printf("%d : %d\n", lara, emir);
    return 0;
}

Emir meint: „Die Funktion satzende, die nach einem Satzgewinn die Sätze hochzählt und die Punkte zurücksetzt, braucht gar keine Zeiger. Sie kann die neuen Werte doch einfach mit return zurückgeben.“

  1. Analysieren Sie, warum der Stand nach den drei Aufrufen noch 0 : 0 ist, und notieren Sie eine korrigierte Fassung von punkt samt Aufruf.
  2. Implementieren Sie die Funktion void satzende(int *pa, int *pb, int *sa, int *sb): Hat einer den Satz gewonnen, wird dessen Satzzahl um 1 erhöht und beide Punktestände werden auf 0 gesetzt; sonst ändert sich nichts. Testen Sie sie in main mit den Ständen 11 : 9 und 11 : 13.
  3. Beurteilen Sie Emirs Vorschlag, satzende ohne Zeiger nur mit return zu schreiben.

Hinweise

Hinweis zu Aufgabe a)
Was genau bekommt punkt beim Aufruf punkt(lara) — die Variable lara selbst oder etwas anderes? Wo liegt stand im Speicher?
Hinweis zu Aufgabe b)
Die Bedingung für einen Satzgewinn von A lautet in Worten: „A hat mindestens 11 Punkte und mindestens 2 mehr als B.“ Schreiben Sie sie mit &&. Achten Sie darauf, im Funktionsrumpf überall *pa statt pa zu schreiben, und rufen Sie die Funktion mit & vor jeder Variablen auf.
Hinweis zu Aufgabe c)
Zählen Sie, wie viele Variablen aus main sich bei einem Satzgewinn ändern — und wie viele Werte eine C-Funktion mit return zurückgeben kann.

Erwartungshorizont

Erwartungshorizont zu Aufgabe a)

C übergibt Parameter als Wert (call by value): stand ist eine eigene lokale Variable, die beim Aufruf eine Kopie von lara erhält. Erhöht wird nur die Kopie; sie verschwindet am Ende der Funktion. lara und emir in main bleiben 0.

Korrektur: Die Funktion bekommt die Adresse und schreibt über den Zeiger zurück.

C · Korrektur
void punkt(int *stand)
{
    *stand = *stand + 1;
}

// Aufruf in main:
punkt(&lara);

Mit punkt(&lara); punkt(&lara); punkt(&emir); gibt das Programm 2 : 1 aus.

Erwartungshorizont zu Aufgabe b)
C · zaehler.c
#include <stdio.h>

void punkt(int *stand)
{
    *stand = *stand + 1;
}

void satzende(int *pa, int *pb, int *sa, int *sb)
{
    if (*pa >= 11 && *pa - *pb >= 2) {
        *sa = *sa + 1;
        *pa = 0;
        *pb = 0;
    } else if (*pb >= 11 && *pb - *pa >= 2) {
        *sb = *sb + 1;
        *pa = 0;
        *pb = 0;
    }
}

void zeige(int a, int b, int sa, int sb)
{
    printf("%d:%d, Saetze %d:%d\n", a, b, sa, sb);
}

int main(void)
{
    int lara = 11, emir = 9, satzL = 0, satzE = 0;
    satzende(&lara, &emir, &satzL, &satzE);
    zeige(lara, emir, satzL, satzE);

    lara = 11;
    emir = 10;
    satzende(&lara, &emir, &satzL, &satzE);
    zeige(lara, emir, satzL, satzE);

    punkt(&emir);
    punkt(&emir);
    punkt(&emir);
    satzende(&lara, &emir, &satzL, &satzE);
    zeige(lara, emir, satzL, satzE);
    return 0;
}
Ausgabe (gcc -Wall zaehler.c -o zaehler)
0:0, Saetze 1:0
11:10, Saetze 1:0
0:0, Saetze 1:1

Bei 11 : 10 ist der Satz noch nicht vorbei (nur 1 Punkt Vorsprung); erst bei 11 : 13 bekommt Emir den Satz. Bewertet werden: richtige Bedingung mit Vorsprung, Zugriff über *, Aufruf mit &, sinnvoller Test.

Erwartungshorizont zu Aufgabe c)

Mit return kann eine Funktion genau einen Wert liefern. Bei einem Satzgewinn ändern sich aber drei Variablen (eine Satzzahl und beide Punktestände), und welche Satzzahl es ist, entscheidet sich erst in der Funktion.

Möglich wäre z. B. int satzende(int a, int b), die nur 0 (weiter), 1 (Satz für A) oder 2 (Satz für B) zurückgibt; das Hochzählen und Zurücksetzen müsste dann in main nach jedem Aufruf wiederholt werden. Das ist ohne Zeiger machbar, verteilt die Regel aber auf mehrere Stellen.

Urteil: Der Vorschlag trägt nur, wenn man die Aufgabe der Funktion verkleinert. Soll satzende den Stand selbst aktualisieren, sind Zeigerparameter der passende Weg. Eine vollständige Antwort nennt die Grenze von return, eine mögliche Alternative und ein begründetes Urteil.