Der Fahrradcomputer
AFB I–IIMia fährt mit dem Rad zur Schule und möchte ihre Fahrten auswerten. Für einen selbst gebauten Fahrradcomputer schreibt sie in C zwei Funktionen: tempo berechnet aus Strecke (in km) und Fahrzeit (in Minuten) die Durchschnittsgeschwindigkeit in km/h, zeigeFahrt gibt eine Fahrt aus. Die Kommentare A bis E markieren Stellen im Quelltext.
#include <stdio.h> double tempo(double strecke, double minuten); // A void zeigeFahrt(double strecke, double minuten); int main(void) { double km = 12.6; double min = 42.0; zeigeFahrt(km, min); // B zeigeFahrt(8.0, 25.0); return 0; } double tempo(double strecke, double minuten) { // C return strecke / minuten * 60; // D } void zeigeFahrt(double strecke, double minuten) { // E printf("Strecke: %5.1f km\n", strecke); printf("Tempo: %5.1f km/h\n", tempo(strecke, minuten)); }
Später möchte Mia auch Pausen berücksichtigen und ergänzt eine Funktion. Sie erwartet danach die Ausgabe „Gesamt: 52 min“.
void mitPause(double minuten) { minuten = minuten + 10; } // in main, nach zeigeFahrt(km, min): mitPause(min); printf("Gesamt: %.0f min\n", min);
- Ordnen Sie den Markierungen A bis E jeweils einen der Begriffe zu: Funktionsdefinition ohne Rückgabewert, Prototyp (Deklaration), Aufruf, Rückgabe eines Werts, Kopf einer Funktionsdefinition mit Rückgabetyp
double. - Berechnen Sie die vier Zeilen, die
fahrrad.causgibt, und notieren Sie sie mit den Leerzeichen der Formatangaben. - Weisen Sie nach, dass nach Mias Ergänzung „Gesamt: 42 min“ ausgegeben wird, und ändern Sie die Funktion
mitPauseund ihren Aufruf so, dass 52 min erscheinen.
Hinweise
Hinweis zu Aufgabe a)
{ … } steht, und ob eine Funktion benutzt oder beschrieben wird.Hinweis zu Aufgabe b)
%5.1f gibt eine Nachkommastelle aus und belegt insgesamt 5 Zeichen.Hinweis zu Aufgabe c)
mitPause beim Aufruf übergeben — die Variable min selbst oder nur ihren Wert? Wie kann eine Funktion ein Ergebnis an den Aufrufer zurückgeben?„Nachweisen“ heißt: mit dem Ablauf im Speicher schlüssig belegen, nicht nur behaupten.Erwartungshorizont
Erwartungshorizont zu Aufgabe a)
| Marke | Begriff |
|---|---|
| A | Prototyp (Deklaration): Kopf mit Semikolon, ohne Rumpf — macht tempo vor main bekannt |
| B | Aufruf von zeigeFahrt mit den Argumenten km und min |
| C | Kopf einer Funktionsdefinition mit Rückgabetyp double |
| D | Rückgabe eines Werts mit return |
| E | Funktionsdefinition ohne Rückgabewert (void) |
Erwartungshorizont zu Aufgabe b)
Fahrt 1: 12,6 km / 42 min · 60 = 0,3 km/min · 60 = 18,0 km/h. Fahrt 2: 8,0 km / 25 min · 60 = 0,32 km/min · 60 = 19,2 km/h.
Strecke: 12.6 km Tempo: 18.0 km/h Strecke: 8.0 km Tempo: 19.2 km/h
Bei %5.1f ist 12.6 vier Zeichen breit, also ein Leerzeichen davor; 8.0 ist drei Zeichen breit, also zwei Leerzeichen davor. Hinzu kommt jeweils das Leerzeichen aus dem Formatstring.
Erwartungshorizont zu Aufgabe c)
Beim Aufruf mitPause(min) wird der Wert 42.0 in den Parameter minuten kopiert (Wertübergabe, call by value). minuten ist eine eigene Variable der Funktion; minuten = minuten + 10 ändert nur diese Kopie auf 52.0. Beim Verlassen der Funktion verschwindet die Kopie, min in main ist unverändert 42.0 — es erscheint „Gesamt: 42 min“ (mit gcc bestätigt).
#include <stdio.h> double mitPause(double minuten) { return minuten + 10; } int main(void) { double min = 42.0; min = mitPause(min); // Rückgabewert übernehmen printf("Gesamt: %.0f min\n", min); // Gesamt: 52 min return 0; }
Entscheidend ist beides: Rückgabetyp double mit return und das Speichern des Ergebnisses beim Aufruf (min = mitPause(min);).
Die Wetterstation im Team
AFB II–IIIDie Wetter-AG stellt ihr Programm auf mehrere Dateien um, damit mehrere Personen gleichzeitig daran arbeiten können. Paul kümmert sich um main.c, Hanna soll die Umrechnungen liefern: inFahrenheit rechnet Grad Celsius in Grad Fahrenheit um (\( F = C \cdot 1{,}8 + 32 \)), windstufe ordnet einer Windgeschwindigkeit in km/h eine vereinfachte Stufe zu: unter 2 km/h Stufe 0 (still), unter 20 km/h Stufe 1 (leicht), unter 50 km/h Stufe 2 (frisch), sonst Stufe 3 (Sturm).
#include <stdio.h> #include "umrechnung.h" int main(void) { double temp = 23.5; // Temperatur in °C double wind = 54.0; // Windgeschwindigkeit in km/h printf("Temperatur: %.1f F\n", inFahrenheit(temp)); printf("Windstufe: %d\n", windstufe(wind)); return 0; }
Paul will schon testen, bevor Hanna fertig ist. Er hat sich eine Header-Datei mit den Prototypen geschrieben und übersetzt:
$ gcc -Wall main.c -o wetter /usr/bin/ld: /tmp/cc62u4aa.o: in function `main': main.c:(.text+0x30): undefined reference to `inFahrenheit' /usr/bin/ld: main.c:(.text+0x5c): undefined reference to `windstufe' collect2: error: ld returned 1 exit status
Jonas meint: „Header-Dateien sind überflüssig. Schreibt einfach #include "umrechnung.c" in main.c und übersetzt nur main.c. Das klappt immer.“
#include "umrechnung.h".- Erstellen Sie die beiden Dateien
umrechnung.h(mit Include-Guard) undumrechnung.cso, dass sie zu Paulsmain.cpassen. - Erklären Sie Pauls Fehlermeldung und die Ausgabe, die das Programm mit dem richtigen Übersetzungsbefehl liefert.
- Erweitern Sie das Projekt um eine Funktion
void warnung(double kmh), die bei Windstufe 3 „Achtung: Sturm! Fenster schliessen.“ ausgibt und sonst nichts. Notieren Sie alle nötigen Änderungen in den drei Dateien. - Erörtern Sie Jonas’ Vorschlag.
Hinweise
Hinweis zu Aufgabe a)
.c-Datei die vollständigen Definitionen. Der Include-Guard besteht aus drei Präprozessor-Zeilen.Hinweis zu Aufgabe b)
ld. Welche Datei hat Paul dem Befehl gcc nicht mitgegeben? Rechnen Sie 23,5 °C selbst um.Hinweis zu Aufgabe c)
umrechnung.c. Nutzen Sie windstufe, statt die Grenze 50 km/h ein zweites Mal hinzuschreiben. Denken Sie an stdio.h.Hinweis zu Aufgabe d)
.c-Datei die Funktionen braucht oder beide .c-Dateien übersetzt werden? Arbeitsteilung, Übersicht über die Schnittstelle, Übersetzungszeit.„Erörtern“ heißt: Argumente dafür und dagegen gegenüberstellen und zu einem begründeten Ergebnis kommen.Erwartungshorizont
Erwartungshorizont zu Aufgabe a)
#ifndef UMRECHNUNG_H #define UMRECHNUNG_H double inFahrenheit(double celsius); int windstufe(double kmh); #endif
#include "umrechnung.h" double inFahrenheit(double celsius) { return celsius * 1.8 + 32; } int windstufe(double kmh) { if (kmh < 2) { return 0; // still } else if (kmh < 20) { return 1; // leicht } else if (kmh < 50) { return 2; // frisch } else { return 3; // Sturm } }
Mit Pauls main.c ohne Warnung übersetzt. Der Include-Guard verhindert, dass die Prototypen doppelt eingefügt werden, falls die Header-Datei mehrfach eingebunden wird. Der Name des Makros ist frei wählbar, muss aber eindeutig sein.
Erwartungshorizont zu Aufgabe b)
Paul hat nur main.c übersetzt. Durch #include "umrechnung.h" kennt der Compiler die Prototypen, deshalb gibt es keinen Compilerfehler. Der Linker (ld) muss aber die fertigen Funktionen inFahrenheit und windstufe einbauen und findet sie nirgends: „undefined reference“. Richtig ist:
$ gcc -Wall main.c umrechnung.c -o wetter $ ./wetter Temperatur: 74.3 F Windstufe: 3
Rechnung: 23,5 · 1,8 + 32 = 42,3 + 32 = 74,3. 54 km/h ist nicht kleiner als 50, also Stufe 3.
Erwartungshorizont zu Aufgabe c)
#ifndef UMRECHNUNG_H #define UMRECHNUNG_H double inFahrenheit(double celsius); int windstufe(double kmh); void warnung(double kmh); #endif
#include <stdio.h> // neu: für printf in warnung #include "umrechnung.h" /* inFahrenheit und windstufe unverändert wie in a) */ void warnung(double kmh) { if (windstufe(kmh) == 3) { printf("Achtung: Sturm! Fenster schliessen.\n"); } }
printf("Windstufe: %d\n", windstufe(wind)); warnung(wind); // 54 km/h: Warnung erscheint warnung(15.0); // Test: keine Ausgabe erwartet return 0;
$ gcc -Wall main.c umrechnung.c -o wetter $ ./wetter Temperatur: 74.3 F Windstufe: 3 Achtung: Sturm! Fenster schliessen.
Der zweite Aufruf mit 15 km/h erzeugt wie gewünscht keine Ausgabe. Weil warnung windstufe aufruft, steht die Sturmgrenze nur an einer Stelle.
Erwartungshorizont zu Aufgabe d)
Dafür: In diesem kleinen Projekt funktioniert es tatsächlich — gcc main.c -o wetter übersetzt dann ohne Fehler, weil der Präprozessor den ganzen Quelltext von umrechnung.c in main.c einfügt. Man spart sich eine Datei und muss beim Übersetzen nur eine Datei angeben.
Dagegen: Übersetzt jemand wie üblich gcc main.c umrechnung.c -o wetter, sind alle Funktionen zweimal definiert; der Linker bricht mit „multiple definition of inFahrenheit“ ab (mit gcc geprüft). Dasselbe passiert, sobald eine zweite .c-Datei die Funktionen nutzen will. Außerdem fehlt die kurze Übersicht der Schnittstelle, die der Header bietet: Paul konnte nur deshalb schon vor Hanna arbeiten. Bei großen Projekten müsste bei jeder Änderung alles neu übersetzt werden.
Ergebnis: Der Vorschlag ist höchstens für Ein-Personen-Miniprojekte vertretbar; „klappt immer“ ist falsch. Für Teamarbeit ist die Aufteilung in .h und .c klar vorzuziehen. Vollständig ist die Erörterung mit Argumenten beider Seiten, dem Linker-Problem und einem begründeten Ergebnis.
