MINT lernen

Textaufgaben: Funktionen und Header

Mia wundert sich an ihrem Fahrradcomputer über eine Pause, die nicht mitzählt, und die Wetter-AG verteilt ihr Programm auf Header- und Quelltextdateien.

Dein Fortschritt:
0 / 0 Aufgaben
1

Der Fahrradcomputer

AFB I–II

Mia 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.

C · fahrrad.c
#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“.

C · Mias Ergänzung
void mitPause(double minuten) {
    minuten = minuten + 10;
}

// in main, nach zeigeFahrt(km, min):
mitPause(min);
printf("Gesamt: %.0f min\n", min);
  1. 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.
  2. Berechnen Sie die vier Zeilen, die fahrrad.c ausgibt, und notieren Sie sie mit den Leerzeichen der Formatangaben.
  3. Weisen Sie nach, dass nach Mias Ergänzung „Gesamt: 42 min“ ausgegeben wird, und ändern Sie die Funktion mitPause und ihren Aufruf so, dass 52 min erscheinen.

Hinweise

Hinweis zu Aufgabe a)
Achten Sie darauf, ob hinter der Klammer ein Semikolon oder ein Block { … } steht, und ob eine Funktion benutzt oder beschrieben wird.
Hinweis zu Aufgabe b)
Rechnen Sie Minuten in Stunden um: km / min · 60. %5.1f gibt eine Nachkommastelle aus und belegt insgesamt 5 Zeichen.
Hinweis zu Aufgabe c)
Was bekommt 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)
MarkeBegriff
APrototyp (Deklaration): Kopf mit Semikolon, ohne Rumpf — macht tempo vor main bekannt
BAufruf von zeigeFahrt mit den Argumenten km und min
CKopf einer Funktionsdefinition mit Rückgabetyp double
DRückgabe eines Werts mit return
EFunktionsdefinition 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.

Ausgabe (mit gcc geprüft)
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).

C · Korrektur mit Rückgabewert (mit gcc getestet)
#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);).

2

Die Wetterstation im Team

AFB II–III

Die 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).

C · main.c (Paul)
#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:

Konsole
$ 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.“

Aufteilung auf drei Dateien
umrechnung.hPrototypen: ?main.cmain (gegeben)umrechnung.cDefinitionen: ?#include#include
Die gestrichelten Pfeile stehen für #include "umrechnung.h".
  1. Erstellen Sie die beiden Dateien umrechnung.h (mit Include-Guard) und umrechnung.c so, dass sie zu Pauls main.c passen.
  2. Erklären Sie Pauls Fehlermeldung und die Ausgabe, die das Programm mit dem richtigen Übersetzungsbefehl liefert.
  3. 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.
  4. Erörtern Sie Jonas’ Vorschlag.

Hinweise

Hinweis zu Aufgabe a)
Die Header-Datei enthält nur Prototypen (mit Semikolon), die .c-Datei die vollständigen Definitionen. Der Include-Guard besteht aus drei Präprozessor-Zeilen.
Hinweis zu Aufgabe b)
Die Meldung kommt nicht vom Compiler, sondern von ld. Welche Datei hat Paul dem Befehl gcc nicht mitgegeben? Rechnen Sie 23,5 °C selbst um.
Hinweis zu Aufgabe c)
Eine neue Funktion braucht einen Prototyp im Header und eine Definition in umrechnung.c. Nutzen Sie windstufe, statt die Grenze 50 km/h ein zweites Mal hinzuschreiben. Denken Sie an stdio.h.
Hinweis zu Aufgabe d)
Mögliche Gesichtspunkte: Funktioniert es in diesem Beispiel? Was passiert, wenn später eine zweite .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)
C · umrechnung.h
#ifndef UMRECHNUNG_H
#define UMRECHNUNG_H

double inFahrenheit(double celsius);
int windstufe(double kmh);

#endif
C · umrechnung.c
#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:

Konsole (mit gcc geprüft)
$ 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)
C · umrechnung.h (erweitert)
#ifndef UMRECHNUNG_H
#define UMRECHNUNG_H

double inFahrenheit(double celsius);
int windstufe(double kmh);
void warnung(double kmh);

#endif
C · umrechnung.c (Ergänzung)
#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");
    }
}
C · main.c (Ende von main)
    printf("Windstufe:  %d\n", windstufe(wind));
    warnung(wind);           // 54 km/h: Warnung erscheint
    warnung(15.0);           // Test: keine Ausgabe erwartet
    return 0;
Konsole (mit gcc geprüft)
$ 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.