Ein Fahrradcomputer im Test
AFB I–IIJonas 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.
#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; }
int belegt hier typisch 4 Byte, ein Zeiger 8 Byte.- Beschreiben Sie, was die Anweisungen (1) bis (4) jeweils bewirken.
- Bestimmen Sie die Ausgabe des Programms sowie die Adresse, die am Ende in
zsteht (Adressen wie in der Skizze). - 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)
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)
strecke, tempo und z und tragen Sie nach jeder Anweisung die Werte ein.Hinweis zu Aufgabe c)
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)
| nach | strecke | tempo | z |
|---|---|---|---|
| (1) | 12 | 18 | 0x2000 |
| (2) | 17 | 18 | 0x2000 |
| (3) | 17 | 18 | 0x2004 |
| (4) | 17 | 36 | 0x2004 |
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'.
Der Tischtennis-Zähler
AFB II–IIILara 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:
#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.“
- Analysieren Sie, warum der Stand nach den drei Aufrufen noch
0 : 0ist, und notieren Sie eine korrigierte Fassung vonpunktsamt Aufruf. - 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 inmainmit den Ständen 11 : 9 und 11 : 13. - Beurteilen Sie Emirs Vorschlag,
satzendeohne Zeiger nur mitreturnzu schreiben.
Hinweise
Hinweis zu Aufgabe a)
punkt beim Aufruf punkt(lara) — die Variable lara selbst oder etwas anderes? Wo liegt stand im Speicher?Hinweis zu Aufgabe b)
&&. 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)
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.
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)
#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; }
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.
