vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Woche 1

Tag 4

Funktionen

Funktionen nehmen in der C-Programmierung und der Philosophie der C- Programmerstellung eine zentrale Stellung ein. Sie haben inzwischen schon einige der Bibliotheksfunktionen von C kennen gelernt. Dabei handelt es sich um fertige, vordefinierte Funktionen, die mit Ihrem Compiler ausgeliefert wurden. Gegenstand des heutigen Kapitels sind allerdings die so genannten benutzerdefinierten Funktionen, die, wie der Name schon verrät, von Ihnen, dem Programmierer, erzeugt werden. Heute lernen Sie:

Was ist eine Funktion?

Heute erhalten Sie auf die Frage »Was ist eine Funktion?« zwei Antworten. Erst erfahren Sie, was Funktionen sind, und danach zeige ich Ihnen, wie sie verwendet werden.

Definition einer Funktion

Kommen wir zuerst zu der Definition: Eine Funktion ist ein benanntes, unabhängiges C-Codefragment, das eine bestimmte Aufgabe ausführt und optional einen Wert an das aufrufende Programm zurückliefert. Werfen wir einen Blick auf die einzelnen Teile dieser Definition:

Soviel zum theoretischen Teil. Merken Sie sich die obige Definition für den nächsten Abschnitt.

Veranschaulichung

Listing 4.1 enthält eine benutzerdefinierte Funktion.

Listing 4.1: Ein Programm mit einer Funktion, die die Kubikzahl einer Zahl berechnet.

1:   /* Beispiel für eine einfache Funktion */
2: #include <stdio.h>
3:
4: long kubik(long x);
5:
6: long eingabe, antwort;
7:
8: int main(void)
9: {
10: printf("Geben Sie eine ganze Zahl ein: ");
11: scanf("%ld", &eingabe);
12: antwort = kubik(eingabe);
13: /* Hinweis: %ld ist der Konversionsspezifizierer für */
14: /* einen Integer vom Typ long */
15: printf("\nDie Kubikzahl von %ld ist %ld.\n", eingabe, antwort);
16:
17: return 0;
18: }
19:
20: /* Funktion: kubik() - Berechnet die Kubikzahl einer Variablen */
21: long kubik(long x)
22: {
23: long x_cubed;
24:
25: x_cubed = x * x * x;
26: return x_cubed;
27: }

Geben Sie eine ganze Zahl ein: 100

Die Kubikzahl von 100 ist 1000000.
Geben Sie eine ganze Zahl ein: 9

Die Kubikzahl von 9 ist 729.
Geben Sie eine ganze Zahl ein: 3

Die Kubikzahl von 3 ist 27.

Die folgende Analyse erläutert nicht das ganze Programm, sondern beschränkt sich auf die Teile des Programms, die direkt mit der Funktion in Zusammenhang stehen.

Zeile 4 enthält den Funktionsprototyp, das heißt das Muster einer Funktion, die erst später in dem Programm auftaucht. Der Prototyp einer Funktion enthält den Namen der Funktion, eine Liste der Variablen, die ihr übergeben werden müssen, und den Typ der Variablen, die sie eventuell zurückgibt. Zeile 4 können Sie entnehmen, dass die Funktion kubik heißt, eine Variable vom Typ long benötigt und einen Wert vom Typ long zurückliefert. Die Variablen, die der Funktion übergeben werden, nennt man auch Argumente. Man gibt sie in Klammern hinter dem Namen der Funktion an. In diesem Beispiel lautet das Argument der Funktion long x. Das Schlüsselwort vor dem Namen der Funktion gibt an, welchen Variablentyp die Funktion zurückliefert. In diesem Fall ist es eine Variable vom Typ long.

Zeile 12 ruft die Funktion kubik auf und übergibt ihr den Wert der Variablen eingabe als Argument. Der Rückgabewert der Funktion wird der Variablen antwort zugewiesen. Beachten Sie, dass in Übereinstimmung mit dem Funktionsprototyp in Zeile 4 sowohl eingabe als auch antwort in Zeile 6 als Variablen vom Typ long deklariert wurden.

Die Funktion selbst wird auch Funktionsdefinition genannt. In diesem Fall heißt sie kubik und steht in den Zeilen 21 bis 27. Wie schon der Prototyp besteht auch die Funktionsdefinition aus mehreren Teilen. Die Funktion beginnt mit dem Funktions-Header in Zeile 21. Der Funktions-Header bildet den Anfang einer Funktion und gibt den Funktionsnamen (hier kubik) an. Außerdem enthält er den Rückgabetyp der Funktion und beschreibt ihre Argumente. Beachten Sie, dass der Funktions-Header mit dem Funktionsprototyp identisch ist (bis auf das Semikolon).

Der Rumpf der Funktion (Zeilen 22 bis 27) ist von geschweiften Klammern umschlossen. Der Rumpf enthält Anweisungen, wie in Zeile 25, die bei jedem Aufruf der Funktion ausgeführt werden. Zeile 23 enthält eine Variablendeklaration, die äußerlich den bereits besprochenen Deklarationen gleicht, jedoch einen kleinen Unterschied aufweist: Sie ist lokal. Lokale Variablen werden innerhalb eines Funktionsrumpfes deklariert. (Lokale Deklarationen werden außerdem noch am Tag 11, »Gültigkeitsbereiche von Variablen«, besprochen.) Den Abschluss der Funktion bildet die return-Anweisung in Zeile 26, die das Ende der Funktion anzeigt. Eine return-Anweisung gibt einen Wert an das aufrufende Programm zurück. In diesem Fall wird der Wert der Variablen x_cubed zurückgeliefert.

Wenn Sie die Struktur der Funktion kubik() mit der von main() vergleichen, werden Sie feststellen, dass es keinen Unterschied gibt. main() ist ebenfalls eine Funktion. Andere Funktionen, die Sie bereits verwendet haben, lauten printf() und scanf(). printf() und scanf() sind zwar Bibliotheksfunktionen (im Gegensatz zu benutzerdefinierten Funktionen), aber auch sie können, wie die von Ihnen erzeugten Funktionen, Argumente übernehmen und Werte zurückgeben.

Funktionsweise einer Funktion

Ein C-Programm führt die Anweisungen in einer Funktion erst aus, wenn die Funktion von einem anderen Teil des Programms aufgerufen wird. Wenn eine Funktion aufgerufen wird, kann das Programm der Funktion in Form eines oder mehrerer Argumente Informationen übergeben. Bei einem Argument handelt es sich um Programmdaten, die von der Funktion zur Ausführung benötigt werden. Anschließend werden die Anweisungen in der Funktion abgearbeitet, wodurch die Funktion die ihr zugewiesene Aufgabe erledigt. Nachdem alle Funktionsanweisungen bearbeitet worden sind, springt die Ausführung zurück zu der Stelle im Programm, an der die Funktion aufgerufen wurde. Funktionen können in Form eines Rückgabewertes Informationen an das Programm zurückliefern.

In Abbildung 4.1 sehen Sie ein Programm mit drei Funktionen, die jeweils einmal aufgerufen werden. Jedes Mal, wenn eine Funktion aufgerufen wird, springt die Programmausführung in die betreffende Funktion. Wenn die Funktion beendet ist, springt die Ausführung zurück an die Stelle, von wo die Funktion aufgerufen wurde. Eine Funktion kann beliebig oft und in beliebiger Reihenfolge aufgerufen werden.

Jetzt wissen Sie, was man unter Funktionen versteht und wie wichtig sie sind. Etwas weiter hinten werde ich Ihnen zeigen, wie Sie ihre eigenen Funktionen erstellen und verwenden.

Abbildung 4.1:  Wenn ein Programm eine Funktion aufruft, springt die Programmausführung in die Funktion und kehrt anschließend wieder zum aufrufenden Programm zurück.

Funktionen

Funktionsprototyp

rueckgabe_typ funktion_name( arg-typ name-1,...,arg-typ name-n);

Funktionsdefinition

rueckgabe_typ funktion_name( arg-typ name-1,...,arg-typ name-n)
{
/* Anweisungen; */
}

Der Funktionsprototyp liefert dem Compiler die Beschreibung einer Funktion, die erst zu einem späteren Zeitpunkt im Programm definiert wird. Der Prototyp umfasst den Rückgabetyp, der den Typ der Variablen angibt, die von der Funktion zurückgeliefert wird, sowie den Funktionsnamen, der widerspiegeln sollte, wozu die Funktion dient. Außerdem enthält der Prototyp die Variablentypen der Argumente (arg-typ), die der Funktion übergeben werden sollen. Wer möchte, kann im Prototyp auch die Namen der zu übergebenden Variablen angeben. Ein Prototyp sollte immer mit einem Semikolon abgeschlossen werden.

Bei der Funktionsdefinition handelt es sich um die eigentliche Funktion. Die Definition enthält den Code, der auszuführen ist. Wenn der Prototyp die Namen der Variablen angibt, sollte die erste Zeile der Funktionsdefinition, der so genannte Funktions-Header, mit dem Funktionsprototyp bis auf das Semikolon übereinstimmen. Ein Funktions-Header sollte kein Semikolon aufweisen. Während die Angabe von Variablennamen für die Argumente im Prototyp optional ist, ist die Aufführung der Variablennamen im Funktions-Header obligatorisch. Auf den Header folgt der Funktionsrumpf mit den Anweisungen, die die Funktion ausführen soll. Der Funktionsrumpf sollte mit einer öffnenden geschweiften Klammer beginnen und mit einer schließenden geschweiften Klammer enden. Alle Funktionen, deren Rückgabetyp nicht void ist, sollten eine return-Anweisung enthalten, die einen Wert zurückliefert, der diesem Typ entspricht.

Beispiele für Funktionsprototypen:
double quadriert( double zahl );
void bericht_ausgeben( int bericht_zahl );
int menue_option_einlesen( void );
Beispiele für Funktionsdefinitionen:
double quadriert( double zahl )        /* Funktions-Header                */
{ /* öffnende geschweifte Klammer */
return( zahl * zahl ); /* Funktionsrumpf */
} /* schließende geschweifte Klammer */
void bericht_ausgeben( int bericht_zahl )
{
if( bericht_zahl == 1 )
puts( "Ausgabe des Berichts 1" );
else
puts( "Bericht 1 wird nicht ausgegeben" );
}

Funktionen und strukturierte Programmierung

Durch die Verwendung von Funktionen in Ihren C-Programmen können Sie sozusagen strukturiert programmieren, das heißt, einzelne Programmaufgaben werden von unabhängigen Codeabschnitten ausgeführt. »Unabhängige Codeabschnitte« klingt ziemlich nach der Definition, die ich Ihnen für die Funktionen gegeben haben, nicht wahr? Funktionen und strukturierte Programmierung sind eng miteinander verbunden.

Die Vorteile der strukturierten Programmierung

Was ist so toll an der strukturierten Programmierung? Zwei wichtige Gründe sprechen für die strukturierte Programmierung:

Ein weiterer Vorteil der strukturierten Programmierung ist die damit verbundene Zeitersparnis. Wenn Sie eine Funktion schreiben, die eine bestimmte Aufgabe in einem Programm lösen soll, können Sie diese Funktion schnell und problemlos in einem anderen Programm verwenden, in der die gleiche Aufgabe gelöst werden muss. Auch wenn sich das Problem im neuen Programm etwas anders darstellt, werden Sie oft die Erfahrung machen, dass es einfacher ist, eine bereits bestehende Funktion zu ändern als sie ganz neu zu schreiben. Überlegen Sie mal, wie oft Sie die beiden Funktionen printf() und scanf() verwendet haben, ohne den dahinter liegenden Code überhaupt zu kennen. Wenn Sie Ihre Funktionen so schreiben, dass sie nur eine bestimmte Aufgabe ausführen, können Sie sie später leichter in anderen Programmen wiederverwenden.

Planung eines strukturierten Programms

Wenn Sie vorhaben, ein strukturiertes Programm zu schreiben, sollten Sie etwas Zeit in die Planung investieren. Planen Sie Ihre Programme unbedingt, bevor Sie mit dem Aufsetzen des Codes beginnen. In der Regel benötigen Sie dafür nur einen Stift und ein Blatt Papier. Das Ergebnis der Planung sollte eine Liste der speziellen Aufgaben sein, die Ihr Programm ausführen soll. Beginnen Sie mit dem anvisierten globalen Ziel des Programms selbst. Was soll zum Beispiel ein Programm tun, das Ihre Namen- und Adressenliste verwalten soll? Hier einige naheliegenden Aufgaben:

Mit dieser Liste haben Sie das Programm in vier Hauptaufgaben zerlegt, für die jeweils eigene Funktionen implementiert werden können. Jetzt können Sie noch einen Schritt weiter gehen und diese Aufgaben in weitere Teilaufgaben zerlegen. So ließe sich zum Beispiel die Aufgabe »Neue Namen und Adressen aufnehmen« in folgende Teilaufgaben gliedern:

Auf gleiche Weise könnten Sie auch die Aufgabe »Bestehende Einträge ändern« wie folgt unterteilen:

Vielleicht ist Ihnen aufgefallen, dass diese beiden Listen zwei Teilaufgaben gemeinsam haben - und zwar die Aufgaben zum Einlesen und Zurückschreiben von Daten auf die Festplatte. Wenn Sie zu der Aufgabe »Die bestehende Adressliste von der Festplatte einlesen« eine Funktion schreiben, können Sie diese Funktion in beiden Funktionen, »Neue Namen und Adressen aufnehmen« und »Bestehende Einträge ändern«, aufrufen. Das Gleiche gilt für die Teilaufgabe »Die aktualisierte Liste auf die Festplatte zurückschreiben«.

Damit dürfte Ihnen zumindest ein Vorteil der strukturierten Programmierung klar sein. Durch sorgfältiges Zerlegen des Programms in Aufgaben ergeben sich mitunter Programmteile, die gemeinsame Aufgaben zu erledigen haben. In unserem Fall können Sie eine »doppelt nutzbare« Festplattenzugriffsfunktion schreiben, die Ihnen Zeit spart und Ihre Programme kleiner und effizienter macht.

Diese Art der Programmierung hat eine hierarchische oder geschichtete Programmstruktur zur Folge. Abbildung 4.2 veranschaulicht die hierarchische Programmierung für das Adresslisten-Programm.

Abbildung 4.2:  Ein strukturiertes Programm ist hierarchisch organisiert.

Wenn Sie diesen Ansatz der Vorplanung verfolgen, erhalten Sie schnell eine Liste der einzelnen Aufgaben, die Ihr Programm zu erledigen hat. Sie können anschließend darangehen, die Aufgaben eine nach der anderen zu lösen, wobei Sie Ihre Aufmerksamkeit jeweils nur auf eine relativ einfache Aufgabe konzentrieren müssen. Wenn diese Funktion dann geschrieben ist und ordnungsgemäß funktioniert, können Sie sich der nächsten Aufgabe widmen. Und bevor Sie sich versehen, nimmt Ihr Programm Formen an.

Der Top-down-Ansatz

Bei der strukturierten Programmierung folgt man dem Top-down-Ansatz (von oben nach unten). In Abbildung 4.2, in der die Programmstruktur einem umgedrehten Baum ähnelt, ist dieser Ansatz veranschaulicht. Häufig wird der Großteil der Arbeit in einem Programm von den Funktionen an den Spitzen der »Äste« erledigt. Die Funktionen näher am »Stamm« dienen vornehmlich dazu, die Programmausführung zu steuern.

Als Folge haben viele C-Programme nur wenig Code im Hauptteil des Programms - das heißt in main(). Der größte Teil des Codes befindet sich in den Funktionen. In main() finden Sie vielleicht nur ein paar Dutzend Codezeilen, die die Programmausführung steuern. Viele Programme präsentieren dem Anwender ein Menü. Dann verzweigt die Programmausführung je nach Auswahl des Anwenders. Jeder Menüzweig führt zu einer eigenen Funktion.

Die Verwendung von Menüs ist ein guter Ansatz für den Programmentwurf. Am Tag 12, »Fortgeschrittene Programmsteuerung«, erfahren Sie, wie Sie mit switch-Anweisungen ein vielseitiges, menügesteuertes System erzeugen können.

Jetzt, da Sie wissen, was Funktionen sind und warum sie so wichtig sind, ist es an der Zeit, Ihnen zu zeigen, wie Sie eigene Funktionen schreiben.

Was Sie tun sollten

Was nicht

Machen Sie erst einen Plan, bevor Sie Code aufsetzen. Indem Sie im Vorfeld eine Programmstruktur festlegen, können Sie beim anschließenden Programmieren und Debuggen Zeit sparen

Versuchen Sie nicht, alles in eine Funktion zu packen. Eine Funktion sollte nur eine Aufgabe ausführen, wie zum Beispiel das Einlesen von Informationen von einer Datei.

Eine Funktion schreiben

Der erste Schritt beim Schreiben einer Funktion besteht darin, sich klarzumachen, worin die Aufgabe der Funktion überhaupt bestehen soll. Wenn Sie das wissen, ist das eigentliche Aufsetzen der Funktion gar nicht mehr so schwierig.

Der Funktions-Header

Die erste Zeile einer jeden Funktion ist der Funktions-Header, der aus drei Teilen besteht, die jeweils eine bestimmte Aufgabe erfüllen. Diese drei Teile sind in Abbildung 4.3 zu sehen und in den folgenden Abschnitten erläutert.

Abbildung 4.3:  Die drei Komponenten eines Funktions-Headers.

Der Rückgabetyp einer Funktion

Der Rückgabetyp einer Funktion gibt den Datentyp an, den die Funktion an das aufrufende Programm zurückliefert. Dieser Rückgabetyp kann ein beliebiger Datentyp von C sein: char, int, long, float oder double. Sie können aber auch eine Funktion definieren, die keinen Wert zurückliefert. In einem solchen Fall muss der Rückgabetyp void lauten. Sehen Sie einige Beispiele:

int funk1(...)          /* Gibt den Typ int zurück.   */
float funk2(...) /* Gibt den Typ float zurück. */
void funk3(...) /* Gibt nichts zurück. */

In diesen Beispielen liefert funk1 einen Integer, funk2 eine Fließkommazahl und funk3 nichts zurück.

Der Funktionsname

Sie können für Ihre Funktionen einen beliebigen Namen wählen, solange er den Regeln für Variablennamen in C entspricht (siehe auch Tag 2, »Die Komponenten eines C-Programms: Quellcode und Daten«). In C-Programmen muss ein Funktionsname eindeutig sein. Er darf nicht einer anderen Funktion oder Variablen zugewiesen werden. Es ist ratsam, einen Namen zu wählen, der beschreibt, was die Funktion macht.

Die Parameterliste

Viele Funktionen verwenden Argumente, die der Funktion beim Aufruf übergeben werden. Eine Funktion muss wissen, welche Art von Argumenten sie zu erwarten hat - sprich den Datentyp jedes Arguments kennen. Sie können für die Argumente jeden Datentyp von C übergeben. Informationen zu den Datentypen der Argumente werden über die Parameterliste des Funktions-Headers bereitgestellt.

Für jedes Argument, das der Funktion übergeben wird, muss die Parameterliste einen Eintrag enthalten. Dieser Eintrag gibt den Datentyp und den Namen des Parameters an. Betrachten wir einmal den Header der Funktion aus Listing 4.1:

long kubik(long x)

Die Parameterliste besteht aus long x, womit ausgedrückt wird, dass diese Funktion ein Argument vom Typ long übernimmt, das in der Funktion durch den Parameter x repräsentiert wird. Wenn es mehr als einen Parameter gibt, müssen die einzelnen Parameter durch Kommata getrennt werden. Der Funktions-Header

void funk1(int x, float y, char z)

spezifiziert eine Funktion mit drei Argumenten: eines vom Typ int namens x, eines vom Typ float namens y und eines vom Typ char namens z. Einige Funktionen erhalten keine Argumente. Dann sollte die Parameterliste als Typ void angeben

int funk2(void)

wie das in der Funktion main(void) der Fall ist.

Achten Sie darauf, hinter dem Funktions-Header kein Semikolon zu setzen. Wenn es Ihnen doch aus Versehen passiert, werden Sie vom Compiler eine Fehlermeldung erhalten.

Manchmal herrscht etwas Verwirrung über den Unterschied zwischen einem Parameter und einem Argument. Ein Parameter ist ein Eintrag in einem Funktions- Header. Er dient als »Platzhalter« für ein Argument. Die Parameter einer Funktion sind unveränderbar, sie ändern sich nicht während der Programmausführung.

Ein Argument ist der eigentliche Wert, der der Funktion von dem aufrufenden Programm übergeben wird. Jedes Mal, wenn eine Funktion aufgerufen wird, können ihr andere Argumente übergeben werden. In C muss der Funktion bei jedem Aufruf die gleiche Anzahl von Argumenten mit dem jeweils festgelegten Typ übergeben werden. Die Argumentwerte jedoch können unterschiedlich sein. In der Funktion wird auf das Argument durch den entsprechenden Parameternamen zugegriffen.

Ein Beispiel soll dies verdeutlichen. Listing 4.2 enthält ein sehr einfaches Programm mit einer Funktion, die zweimal aufgerufen wird.

Listing 4.2: Der Unterschied zwischen Argumenten und Parametern.

1:   /* Demonstriert den Unterschied zwischen Argumenten und Parametern. */
2:
3: #include <stdio.h>
4:
5: float x = 3.5, y = 65.11, z;
6:
7: float haelfte_von(float k);
8:
9: int main(void)
10: {
11: /* In diesem Aufruf ist x das Argument zu haelfte_von(). */
12: z = haelfte_von(x);
13: printf("Der Wert von z = %f\n", z);
14:
15: /* In diesem Aufruf ist y das Argument zu haelfte_von(). */
16: z = haelfte_von(y);
17: printf("Der Wert von z = %f\n", z);
18:
19: return 0;
20: }
21:
22: float haelfte_von(float k)
23: {
24: /* k ist der Parameter. Bei jedem Aufruf von haelfte_von() */
25: /* erhält k den Wert, der als Argument übergeben wurde. */
26:
27: return (k/2);
28: }

Der Wert von z = 1.750000
Der Wert von z = 32.555000

Abbildung 4.4 zeigt die Beziehung zwischen Argumenten und Parametern.

Wie Sie Listing 4.2 entnehmen können, wird der Funktionsprototyp haelfte_von() in Zeile 7 deklariert. Die Zeilen 12 und 16 rufen haelfte_von() auf, und die Zeilen 22 bis 28 enthalten die eigentliche Funktion. In den Zeilen 12 und 16 werden jeweils unterschiedliche Argumente an haelfte_von() übergeben. In Zeile 12 ist es das x, welches einen Wert von 3.5 enthält und in Zeile 16 das y mit dem Wert von 65.11. Wenn das Programm ausgeführt wird, gibt es jeweils die korrekte Zahl aus. Die Werte von x und y werden dem Argument k von haelfte_von() übergeben. Die Übergabe findet genauso statt, als würde man beim ersten Male den Wert von x in k und beim zweiten Mal den Wert von y in k kopieren. Danach wird der jeweilige Wert von haelfte_von() durch 2 geteilt und das Ergebnis zurückgegeben (Zeile 27) .

Abbildung 4.4:  Bei jedem Funktionsaufruf werden die Argumente den Parametern der Funktion übergeben.

Was Sie tun sollten

Was nicht

Wählen Sie für Ihre Funktion einen Namen, der den Zweck der Funktion beschreibt.

Übergeben Sie einer Funktion keine Werte, die nicht benötigt werden.

Versuchen Sie nicht, einer Funktion weniger (oder mehr) Argumente zu übergeben, als durch die Parameter vorgegeben ist. In C-Programmen muss die Anzahl der übergebenen Argumente mit der Zahl der Parameter übereinstimmen.

Der Funktionsrumpf

Der Funktionsrumpf ist von geschweiften Klammern umschlossen und folgt unmittelbar auf den Funktions-Header. Im Funktionsrumpf wird die eigentliche Arbeit getan. Wenn eine Funktion aufgerufen wird, beginnt die Ausführung am Anfang des Rumpfes und endet (das heißt »kehrt zurück zum aufrufenden Programm«), wenn sie auf eine return-Anweisung oder auf eine schließende geschweifte Klammer trifft.

Lokale Variablen

Sie können innerhalb eines Funktionsrumpfes Variablen deklarieren. Variablen, die in einer Funktion deklariert werden, bezeichnet man als lokale Variablen. Der Begriff lokal bedeutet, dass die Variablen privat in Bezug auf diese bestimmte Funktion sind und es zu keinen Überschneidungen mit gleichlautenden Variablen an anderer Stelle im Programm kommt. In Bälde werden ich Ihnen dies näher erklären, aber im Moment sollten Sie erst einmal lernen, wie man lokale Variablen deklariert.

Die Deklaration lokaler Variablen unterscheidet sich nicht von der Deklaration anderer Variablen. Es werden die gleichen Variablentypen verwendet und es gelten die gleichen Regeln für die Namensgebung, die Sie schon am Tag 2 kennen gelernt haben. Lokale Variablen können bei der Deklaration auch initialisiert werden. Sehen Sie im Folgenden ein Beispiel für vier lokale Variablen, die innerhalb einer Funktion deklariert werden:

int funk1(int y)
{
int a, b = 10;
float rate;
double kosten = 12.55;
/* hier steht der Funktionscode... */
}

Die obenstehenden Deklarationen erzeugen die lokalen Variablen a, b, rate und kosten, die dann von dem Code in der Funktion verwendet werden können. Beachten Sie, dass die Funktionsparameter als Variablendeklarationen betrachtet werden. Deshalb sind die Variablen aus der Parameterliste (falls vorhanden) ebenfalls in der Funktion verfügbar.

Wenn Sie eine Variable in einer Funktion deklarieren und verwenden, ist diese völlig getrennt von den anderen Variablen zu sehen, die irgendwo anders im Programm deklariert wurden. Listing 4.3 veranschaulicht diese Unabhängigkeit.

Listing 4.3: Ein Beispiel für lokale Variablen.

1:   /* Ein Beispiel für lokale Variablen. */
2:
3: #include <stdio.h>
4:
5: int x = 1, y = 2;
6:
7: void demo(void);
8:
9: int main(void)
10: {
11: printf("\nVor dem Aufruf von demo(), x = %d und y = %d.", x, y);
12: demo();
13: printf("\nNach dem Aufruf von demo(), x = %d und y = %d\n.", x, y);
14:
15: return 0;
16: }
17:
18: void demo(void)
19: {
20: /* Deklariert und initialisiert zwei lokale Variablen. */
21:
22: int x = 88, y = 99;
23:
24: /* Zeigt die Werte an. */
25:
26: printf("\nIn der Funktion demo(), x = %d und y = %d.", x, y);
27: }

Vor dem Aufruf von demo(), x = 1 und y = 2.
In der Funktion demo(), x = 88 und y = 99.
Nach dem Aufruf von demo(), x = 1 und y = 2.

Listing 4.3 ist den heute bereits vorgestellten Programmen sehr ähnlich. Zeile 5 deklariert die Variablen x und y. Da diese außerhalb einer Funktion deklariert sind, werden sie als global bezeichnet. Zeile 7 enthält den Prototyp für die Beispielfunktion namens demo(). Sie übernimmt keine Parameter und hat deshalb void im Prototyp stehen. Da sie auch keine Werte zurückgibt, lautet der Typ des Rückgabewertes ebenfalls void. In Zeile 9 beginnt die Funktion main(), die sehr einfach ist. Zuerst wird in Zeile 11 printf() aufgerufen, um die Werte von x und y auszugeben. Anschließend wird die Funktion demo() aufgerufen. Beachten Sie, dass demo()in Zeile 22 seine eigenen lokalen Versionen von x und y deklariert. Zeile 26 beweist, dass die lokalen Variablen vor anderen Variablen Vorrang haben. Nachdem die demo-Funktion aufgerufen wurde, werden in Zeile 13 erneut die Werte von x und y ausgegeben. Da wir uns nicht länger in der Funktion demo() befinden, werden die ursprünglichen globalen Werte angezeigt.

Wie Sie feststellen können, sind die lokalen Variablen x und y in der Funktion völlig unabhängig von den globalen Variablen x und y, die außerhalb der Funktion deklariert wurden. Drei Regeln muss man sich im Zusammenhang mit Variablen in Funktionen merken:

Ehrlich gesagt, werden diese Regeln nicht immer befolgt. Später in diesem Buch zeige ich Ihnen, wie Sie diese Regeln umgehen können. Im Moment sollten Sie sich diese Regeln jedoch noch zu Herzen nehmen, um Ärger zu vermeiden.

Funktionen sind unter anderem deshalb unabhängig, weil man die Variablen der Funktion von den anderen Programmvariablen trennt. Eine Funktion kann jede denkbare Datenmanipulation durchführen und dabei ihren eigenen Satz an lokalen Variablen verwenden. Sie brauchen keine Angst zu haben, dass diese Manipulationen unbeabsichtigt andere Teile des Programms beeinflussen.

Funktionsanweisungen

Hinsichtlich der Anweisungen, die in eine Funktion mit aufgenommen werden können, gibt es praktisch keine Beschränkungen. Das Einzige, was Sie vermeiden sollten, ist, eine Funktion innerhalb einer anderen Funktion zu definieren (der GNU-C- Compiler akzeptiert dies zwar, aber auf andere Compiler ist das nicht unbedingt portierbar). Sie können jedoch alle anderen C-Anweisungen verwenden, einschließlich der Schleifen (werden am Tag 5, »Grundlagen der Programmsteuerung«, behandelt), der if-Anweisungen und der Zuweisungen. Und Sie können Bibliotheksfunktionen sowie benutzerdefinierte Funktionen aufrufen.

Wie umfangreich kann eine Funktion sein? In C gibt es keine Längenbeschränkungen für Funktionen, aber es ist zweckmäßig, Ihre Funktionen so kurz wie möglich zu halten. Denken Sie an die strukturierte Programmierung, in der jede Funktion nur eine relativ einfache Aufgabe durchführen soll. Wenn Sie feststellen, dass eine Funktion ziemlich lang wird, ist dies vielleicht ein Indiz dafür, dass die Aufgabe, die Sie damit lösen wollen, für eine Funktion allein zu komplex ist. Wahrscheinlich kann die Aufgabe auf eine oder mehrere kleinere Funktionen aufgeteilt werden.

Wie lang ist zu lang? Auf diese Frage gibt es keine definitive Antwort, aber in der Praxis findet man selten eine Funktion, die länger als 25 bis 30 Codezeilen ist. Die Entscheidung liegt aber ganz bei Ihnen. Einige Programmieraufgaben erfordern längere Funktionen, andere hingegen kommen mit einigen wenigen Zeilen aus. Mit zunehmender Programmierpraxis wird es Ihnen immer leichter fallen, zu entscheiden, wann etwas in kleinere Funktionen zerlegt werden sollte und wann nicht.

Einen Wert zurückgeben

Um einen Wert aus einer Funktion zurückzugeben, verwenden Sie das Schlüsselwort return, gefolgt von einem C-Ausdruck. Wenn die Programmausführung auf eine return-Anweisung stößt, wird der Ausdruck ausgewertet und das Ergebnis an das aufrufende Programm zurückgegegeben. Der Rückgabewert der Funktion ist also der Wert des Ausdrucks. Betrachten wir folgende Funktion:

int funk1(int var)
{
int x;
/* hier steht der Funktionscode ... */
return x;
}

Wenn diese Funktion aufgerufen wird, werden die Anweisungen im Funktionsrumpf bis zu der return-Anweisung ausgeführt. return beendet die Funktion und gibt den Wert von x zurück an das aufrufende Programm. Der Ausdruck, der auf das Schlüsselwort return folgt, kann ein beliebiger gültiger C-Ausdruck sein.

Eine Funktion kann mehrere return-Anweisungen enthalten. Die erste return- Anweisung, die ausgeführt wird, ist die einzige, die von Bedeutung ist. Mehrere return-Anweisungen können eine effiziente Möglichkeit sein, verschiedene Werte aus einer Funktion zurückzugeben. Ein Beispiel hierzu finden Sie in Listing 4.4.

Listing 4.4: Mehrere return-Anweisungen in einer Funktion.

1:   /* Beispiel für mehrere return-Anweisungen in einer Funktion. */
2:
3: #include <stdio.h>
4:
5: int x, y, z;
6:
7: int groesser_von( int a, int b);
8:
9: int main(void)
10: {
11: puts("Zwei verschiedene Integer-Werte eingeben: ");
12: scanf("%d%d", &x, &y);
13:
14: z = groesser_von(x,y);
15:
16: printf("\nDer größere Wert beträgt %d.\n", z);
17:
18: return 0;
19: }
20:
21: int groesser_von( int a, int b)
22: {
23: if (a > b)
24: return a;
25: else
26: return b;
27: }

Zwei verschiedene Integer-Werte eingeben:
200 300

Der größere Wert beträgt 300.
Zwei verschiedene Integer-Werte eingeben:
300
200

Der größere Wert beträgt 300.

Wie schon in den anderen Beispielen beginnt Listing 4.4 mit einem Kommentar, der beschreibt, was das Programm macht (Zeile 1). Die Header-Datei stdio.h wird eingebunden, um die Standardfunktionen für die Ein- und Ausgabe verfügbar zu machen. Mit diesen Funktionen kann das Programm Informationen auf dem Bildschirm anzeigen und Benutzereingaben einlesen. Zeile 7 enthält den Funktionsprototyp für groesser_von(). Beachten Sie, dass die Funktion zwei Variablen vom Typ int als Parameter übernimmt und ein int zurückgibt. Zeile 14 ruft groesser_von() mit x und y auf. Die Funktion groesser_von() enthält mehrere return- Anweisungen. Mit Hilfe einer if-Anweisung prüft die Funktion in Zeile 23, ob a größer ist als b. Wenn ja, führt Zeile 24 eine return-Anweisung aus und die Funktion wird direkt beendet. In diesem Fall werden die Zeilen 25 und 26 ignoriert. Wenn jedoch a nicht größer als b ist, wird Zeile 24 übersprungen, die else-Bedingung greift und die return-Anweisung in Zeile 26 wird ausgeführt. Sie sollten inzwischen verstanden haben, dass - in Abhängigkeit von den Argumenten, die der Funktion groesser_von() übergeben werden - entweder die erste oder die zweite return- Anweisung ausgeführt und der entsprechende Wert zurück an die aufrufende Funktion gegeben wird.

Noch eine Abschlussbemerkung zu diesem Programm. Zeile 11 enthält eine Funktion, die Ihnen vielleicht nicht aufgefallen ist: puts() ist eine einfache Funktion, die einen String auf der Standardausgabe, normalerweise dem Computerbildschirm, anzeigt. Auf Strings gehe ich am Tag 9, »Zeichen und Strings«, ein. Für heute reicht es, wenn Sie wissen, dass es sich dabei um Text in Anführungszeichen handelt.

Denken Sie daran, dass der Typ des Rückgabewertes einer Funktion in dem Funktions-Header und dem Funktionsprototyp festgelegt ist. Der Wert, der von der Funktion zurückgegeben wird, muss vom Typ her identisch sein, oder der Compiler erzeugt einen Fehler.

Die strukturierte Programmierung legt nahe, dass jede Funktion nur einen Einstieg und einen Ausstieg hat. Das bedeutet, dass Sie versuchen sollten, nur eine return-Anweisung in Ihrer Funktion zu verwenden. Manchmal ist jedoch ein Programm mit mehreren return-Anweisungen viel einfacher zu lesen und zu warten. In solchen Fällen sollte die gute Wartbarkeit Vorrang haben.

Der Funktionsprototyp

Ein Programm sollte für jede Funktion einen Prototyp angeben. Ein Beispiel für einen Funktionsprototyp finden Sie in Zeile 4 des Listings 4.1 sowie in den anderen Listings. Was ist ein Funktionsprototyp und wozu dient er?

Von früheren Beispielen wissen Sie, dass der Prototyp einer Funktion mit dem Funktions-Header identisch ist, aber mit einem Semikolon abgeschlossen wird. Deshalb enthält der Funktionsprototyp wie der Funktions-Header Informationen über den Typ des Rückgabewertes, den Namen und die Parameter. Aufgabe des Prototyps ist es, diese Informationen dem Compiler mitzuteilen. Anhand dieser Informationen kann der Compiler bei jedem Aufruf der Funktion überprüfen, ob die der Funktion übergebenen Argumente von der Anzahl und vom Typ her stimmen und ob der Rückgabewert korrekt verwendet wird. Gibt es Abweichungen, erzeugt der Compiler eine Fehlermeldung.

Genau genommen muss ein Funktionsprototyp nicht unbedingt mit dem Funktions- Header übereinstimmen. Die Parameternamen können sich unterscheiden, solange Typ, Anzahl und Reihenfolge der Parameter übereinstimmen. Allerdings gibt es auch keinen Grund, warum Header und Prototyp nicht übereinstimmen sollten, schließlich erleichtert sich dadurch das Lesen des Quellcodes. Auch das Aufsetzen des Programms wird erleichtert. Wenn Sie eine Funktionsdefinition vervollständigt haben, können Sie mit der Ausschneiden-und-Einfügen-Funktion des Editors den Funktions- Header kopieren und so den Prototyp erzeugen. Vergessen Sie aber nicht, das Semikolon hinten anzufügen.

Damit bleibt nur noch die Frage zu klären, wo man die Funktionsprototypen im Quellcode unterbringen soll. Am sinnvollsten ist es, sie vor main() zu stellen oder vor die Definition der ersten Funktion. Der guten Lesbarkeit halber ist es zu empfehlen, alle Prototypen an einer Stelle anzugeben.

Was Sie tun sollten

Was nicht

Verwenden Sie so oft wie möglich lokale Variablen.

Versuchen Sie nicht, einen Wert zurückzugeben, der im Typ von dem Typ der Funktion abweicht.

Beschränken Sie jede Funktion auf eine einzige Aufgabe.

Achten Sie darauf, dass die Funktionen nicht zu lang werden. Wenn eine Funktion zu lang wird, versuchen Sie diese in einzelne kleinere Aufgaben zu zerlegen.

Vermeiden Sie mehrere return- Anweisungen, wenn sie nicht unbedingt notwendig sind. Ein return sollte aber möglichst vorhanden sein. Manchmal jedoch sind mehrere return-Anweisungen einfacher und klarer.

Argumente an eine Funktion übergeben

Um einer Funktion Argumente zu übergeben, müssen Sie sie in Klammern hinter dem Funktionsnamen auflisten. Anzahl und Typen der Argumente müssen mit den Parametern in Funktions-Header und Prototyp übereinstimmen. Wenn zum Beispiel eine Funktion definiert wird, die zwei Argumente vom Typ int übernimmt, müssen Sie ihr auch genau zwei Argumente vom Typ int übergeben - nicht mehr, nicht weniger und auch keinen anderen Typ. Sollten Sie versuchen, einer Funktion eine falsche Anzahl und/oder einen falschen Typ zu übergeben, wird der Compiler dies aufgrund der Informationen im Funktionsprototyp feststellen und bemäkeln.

Wenn die Funktion mehrere Argumente übernimmt, werden die im Funktionsaufruf aufgelisteten Argumente den Funktionsparametern entsprechend ihrer Reihenfolge zugewiesen: das erste Argument zu dem ersten Parameter, das zweite Argument zu dem zweiten Parameter und so weiter, wie in Abbildung 4.5 zu sehen.

Abbildung 4.5:  Mehrere Argumente werden den Funktionsparametern entsprechend ihrer Reihenfolge zugewiesen.

Jedes Argument kann ein beliebiger gültiger C-Ausdruck sein: eine Konstante, eine Variable, ein mathematischer oder logischer Ausdruck oder sogar eine andere Funktion (eine mit einem Rückgabewert). Wenn zum Beispiel haelfte(), quadrat() und drittel() alles Funktionen mit Rückgabewerten wären, könnten Sie folgendes schreiben:

x = haelfte(drittel(quadrat(haelfte(y))));

Das Programm ruft zuerst haelfte() auf und übergibt ihr y als Argument. Wenn die Ausführung von haelfte() zurückkehrt, ruft das Programm quadrat() auf und übergibt der Funktion den Rückgabewert von haelfte() als Argument. Als Nächstes wird drittel() mit dem Rückgabewert von quadrat() als Argument aufgerufen. Anschließend wird erneut die Funktion haelfte() aufgerufen, diesmal jedoch mit dem Rückgabewert von drittel() als Argument. Zum Schluss wird der Rückgabewert von haelfte() der Variablen x zugewiesen. Das folgende Codefragment bewirkt das Gleiche:

a = haelfte(y);
b = quadrat(a);
c = drittel(b);
x = haelfte(c);

Funktionen aufrufen

Es gibt zwei Möglichkeiten, eine Funktion aufzurufen. Jede Funktion kann, wie das folgende Beispiel zeigt, in einer Anweisung, die nur aus dem Funktionsnamen und der Argumentenliste besteht, aufgerufen werden. Wenn die Funktion einen Rückgabewert hat, wird dieser verworfen.

warten(12);

Die zweite Methode kann nur für Funktionen verwendet werden, die einen Rückgabewert haben. Da diese Funktionen sich zu einem Wert auswerten lassen (ihren Rückgabewert), sind sie als gültiger C-Ausdruck zu betrachten und können überall dort verwendet werden, wo auch ein C-Ausdruck verwendet werden kann. Sie haben bereits einen Ausdruck kennen gelernt, der einen Rückgabewert auf der rechten Seite einer Zuweisung verwendete. Sehen Sie im Folgenden noch einige Beispiele.

In diesem Beispiel ist haelfte_von() ein Parameter einer Funktion:

printf("Die Hälfte von %d ist %d.", x, haelfte_von(x));

Zuerst wird die Funktion haelfte_von() mit dem Wert von x aufgerufen und anschließend printf() mit den Werten »Die Hälfte von %d ist %d.«, x und haelfte_von(x) aufgerufen.

In unserem zweiten Beispiel werden mehrere Funktionen in einem Ausdruck verwendet:

y = haelfte_von(x) + haelfte_von(z);

Hier wird haelfte_von() zweimal verwendet, aber der zweite Aufruf könnte auch irgendeiner anderen Funktion gelten. Der folgende Code zeigt die gleiche Anweisung, diesmal jedoch über mehrere Zeilen verteilt:

a = haelfte_von(x);
b = haelfte_von(z);
y = a + b;

Die abschließenden zwei Beispiele zeigen Ihnen, wie Sie die Rückgabewerte von Funktionen effektiv nutzen können. Hier wird eine Funktion mit der if-Anweisung verwendet:

if ( haelfte_von(x) > 10 )
{
/* Anweisungen; */ /* die Anweisungen können beliebig sein! */
}

Wenn der Rückgabewert der Funktion dem Kriterium entspricht (in diesem Fall soll haelfte_von() einen Wert größer als 10 zurückliefern), ist die if-Anweisung wahr und ihre Anweisungen werden ausgeführt. Erfüllt der Rückgabewert das Kriterium nicht, werden die Anweisungen zu if nicht ausgeführt.

Das folgende Beispiel ist sogar noch besser:

if ( einen_prozess_ausfuehren() != OKAY )
{
/* Anweisungen; */ /* Fehlerroutine ausführen */
}

Wieder werden keine eigentlichen Anweisungen gegeben, und einen_prozess_ ausfuehren() ist auch keine richtige Funktion. Dennoch ist dies ein wichtiges Beispiel. Es wird der Rückgabewert eines Prozesses überprüft, um festzustellen, ob er korrekt läuft. Wenn nicht, übernehmen die Anweisungen alle notwendigen Fehlerbehandlungsroutinen oder Aufräumarbeiten. So wird in der Regel vorgegangen, wenn man auf Informationen in Dateien zugreifen, Werte vergleichen oder Speicher allokieren will.

Wenn Sie versuchen, eine Funktion mit dem Rückgabewert
vom Typ void als Ausdruck zu verwenden, erzeugt der Compiler
eine Fehlermeldung.

Was Sie tun sollten

Was nicht

Übergeben Sie Ihren Funktionen Parameter, um die Funktion generisch und damit wiederverwertbar zu machen.

Nutzen Sie die Möglichkeit, Funktionen in Ausdrücken zu verwenden.

Machen Sie eine einzelne Anweisung nicht unnötig komplex, indem Sie eine Reihe von Funktionen darin unterbringen. Sie sollten nur dann Funktionen in Ihren Anweisungen verwenden, wenn diese den Code nicht unverständlich machen.

Rekursion

Der Begriff Rekursion bezieht sich auf Situationen, in denen sich eine Funktion entweder direkt oder indirekt selbst aufruft. Indirekte Rekursion liegt vor, wenn eine Funktion eine andere aufruft, die wiederum die erste Funktion aufruft. In C sind rekursive Funktionen möglich und in manchen Situationen können Sie durchaus nützlich sein.

So kann die Rekursion zum Beispiel eingesetzt werden, um die Fakultät einer Zahl zu berechnen. Die Fakultät der Zahl x schreibt sich x! und berechnet sich wie folgt:

x! = x * (x-1) * (x-2) * (x-3) * ... * (2) * 1

Sie können x! jedoch auch folgendermaßen berechnen:

x! = x * (x-1)!

Gehen wir noch einen Schritt weiter und berechnen wir mit der gleichen Prozedur
(x-1)!:

(x-1)! = (x-1) * (x-2)!

Sie rechnen rekursiv, bis Sie beim Wert 1 landen, in welchem Fall Sie fertig wären. Das Programm in Listing 4.5 verwendet eine rekursive Funktion, um Fakultäten zu berechnen. Da das Programm Integer vom Typ unsigned verwendet, sind nur Eingabewerte bis 14 erlaubt. Die Fakultät von 15 und größeren Werten liegt außerhalb des zulässigen Bereichs für vorzeichenlose Integer.

Listing 4.5: Programm mit einer rekursiven Funktion zur Berechnung von Fakultäten.

1:   /* Beispiel für Funktionsrekursion. Berechnet die */
2: /* Fakultät einer Zahl. */
3:
4: #include <stdio.h>
5:
6: unsigned int f, x;
7: unsigned int fakultaet(unsigned int a);
8:
9: int main(void)
10: {
11: puts("Geben Sie einen Wert zwischen 1 und 14 ein: ");
12: scanf("%d", &x);
13:
14: if( x > 14 || x < 1)
15: {
16: printf("Es sind nur Werte von 1 bis 14 zulässig!\n");
17: }
18: else
19: {
20: f = fakultaet(x);
21: printf("Die Fakultät von %u entspricht %u\n", x, f);
22: }
23:
24: return 0;
25: }
26:
27: unsigned int fakultaet(unsigned int a)
28: {
29: if (a == 1)
30: return 1;
31: else
32: {
33: a *= fakultaet(a-1);
34: return a;
35: }
36: }

Geben Sie einen Wert zwischen 1 und 14 ein:
6
Die Fakultät von 6 entspricht 720

Die erste Hälfte dieses Programms entspricht weitestgehend den anderen Programmen, die Sie inzwischen kennen gelernt haben. Es beginnt mit einem Kommentar in den Zeilen 1 und 2. Zeile 4 bindet die entsprechende Header-Datei für die Eingabe-/Ausgaberoutinen ein. Zeile 6 deklariert eine Reihe von Integer-Werten vom Typ unsigned. Zeile 7 enthält den Funktionsprototyp für die Fakultätsfunktion. Beachten Sie, dass diese Funktion als Parameter den Typ unsigned int übernimmt und den gleichen Typ zurückgibt. In den Zeilen 9 bis 25 steht die Funktion main(). Die Zeile 11 fordert dazu auf, einen Wert zwischen 1 bis 14 einzugeben, und die Zeile 12 übernimmt dann diesen eingegebenen Wert.

Die Zeilen 14 bis 22 weisen eine interessante if-Anweisung auf. Da Werte größer 14 ein Problem darstellen, wird mit dieser if-Anweisung der Wert der Eingabe überprüft. Ist er größer als 14, wird eine Fehlermeldung ausgegeben. Ansonsten errechnet das Programm in Zeile 20 die Fakultät und gibt das Ergebnis in Zeile 21 aus. Wenn Sie wissen, dass sich ein solches Problem stellt (das heißt, die einzugebende Zahl einen bestimmten Wert nicht über- oder unterschreiten darf), sollten Sie Code hinzufügen, der das Problem erkennt und abfängt.

Die rekursive Funktion fakultaet() finden Sie in den Zeilen 27 bis 36. Der übergebene Wert wird a zugewiesen. Zeile 29 prüft den Wert von a. Lautet der Wert 1, gibt das Programm den Wert 1 zurück. Ist der Wert nicht 1, erhält a den Wert a mal der Fakultät von fakultaet(a-1). Daraufhin ruft das Programm die Fakultätsfunktion erneut auf, aber diesmal erhält a den Wert (a-1). Wenn (a-1) immer noch nicht gleich 1 ist, wird fakultaet() noch einmal aufgerufen, diesmal mit ((a-1)-1), was gleichbedeutend mit (a-2) ist. Dieser Vorgang wiederholt sich, bis die if-Anweisung in Zeile 29 wahr wird. Wenn als Wert 3 eingegeben wurde, wird die Fakultät wie folgt ausgewertet:

3 * (3-1) * ((3-1)-1)

Um die Rekursion besser zu verstehen, sollten Sie vielleicht das Programm aus Listing 4.5 in den ddd-Debugger laden. Setzen Sie, wie am Tag 1 beschrieben, einen Haltepunkt bei der ersten Anweisung in Zeile 11, indem Sie zuerst die Zeile ganz links anklicken und dann den Stop-Schalter auf der Werkzeugleiste anklicken. Damit setzen Sie ein Stopzeichen direkt am Anfang der Zeile. Wenn Sie jetzt auf den Schalter Run in dem kleineren, frei platzierbaren Fenster anklicken, erscheint ein grüner Pfeil neben dem Stopzeichen. Jetzt können Sie mit dem Schalter Step im frei platzierbaren Fenster Ihr Programm schrittweise analysieren.

Sicher ist Ihnen aufgefallen, dass die Ausgabe Ihres Programms im unteren Teil Ihres Hauptfensters erscheint. Wenn Sie bei der Zeile mit scanf() angekommen sind und den Schalter Step angeklickt haben, hält das Programm an und wartet darauf, dass Sie eine Zahl eingeben. Klicken Sie dazu in den unteren Teil des Hauptfensters von ddd, das die Ausgabe Ihres Programms anzeigt, und geben Sie eine Zahl zwischen 1 und 14 ein. Betätigen Sie dann die Eingabetaste. Der grüne Pfeil sollte jetzt zur ersten if-Anweisung der Funktion main() weiter wandern.

Jetzt können Sie den Rest des Programms durchgehen. Beachten Sie, dass, wenn der Debugger in die Funktion fakultaet() springt, der untere Teil des Hauptfensters den Funktionsnamen und den Wert des Arguments, der der Funktion übergeben wird, anzeigt. Wenn Sie sich innerhalb der Funktion fakultaet() befinden und den Cursor über das a (den Namen der Variable) halten, wird der Wert der Variablen unten im ddd-Fenster angezeigt. Beim Durchlaufen der Funktion fakultaet() werden Sie feststellen, dass der Wert bei jedem Aufruf der Funktion abnimmt, bis er gleich 1 ist. Anschließend wird der Debugger den berechneten Wert von a zurückgeben, egal wie oft die Funktion aufgerufen wurde. Gehen Sie dieses Programm mehrmals durch, um ein Gefühl dafür zu bekommen, wie Rekursionen funktionieren.

Was Sie tun sollten

Was nicht

Entwickeln Sie erst ein Verständnis für Rekursionen und machen Sie sich damit vertraut, bevor Sie sie in einem Programm verwenden, das Sie vertreiben wollen.

Verwenden Sie keine Rekursion, wenn es extrem viele Iterationen gibt. (Eine Iteration ist die Wiederholung einer Programmanweisung). Die Rekursion benötigt viele Ressourcen, da sich die Funktion merken muss, wo in der Rekursion sie sich gerade befindet.

Wo werden Funktionen definiert?

Vielleicht fragen Sie sich inzwischen, wo Sie Funktionsdefinitionen in Ihrem Quelltext am geschicktesten unterbringen. Im Moment sollten Sie sie in die Quelltextdatei mit aufnehmen, in der auch main() steht, wobei sie hinter main() selbst gesetzt werden sollten. Abbildung 4.6 veranschaulicht die grundlegende Struktur eines Programms, das Funktionen verwendet.

Abbildung 4.6:  Setzen Sie Ihre Funktionsprototypen vor main() und Ihre Funktionsdefinitionen hinter main().

Sie können Ihre benutzerdefinierten Funktionen auch in einer separaten Quelltextdatei, getrennt von main(), unterbringen. Diese Technik ist für umfangreiche Programme nützlich und für den Fall, dass Sie den gleichen Satz an Funktionen in mehr als nur einem Programm verwenden wollen. Mehr dazu erfahren Sie am Tag 20, »Compiler für Fortgeschrittene«.

Zusammenfassung

Dieses Kapitel hat Ihnen einen wichtigen Bestandteil der C-Programmierung vorgestellt: die Funktionen. Funktionen sind unabhängige Code-Abschnitte, die spezielle Aufgaben durchführen. Wenn in Ihrem Programm eine Aufgabe zu bewältigen ist, ruft das Programm die für diese Aufgabe konzipierte Funktion auf. Die Verwendung von Funktionen ist eine wesentliche Voraussetzung für die strukturierte Programmierung - ein bestimmtes Programmdesign, das einen modularen Ansatz von oben nach unten propagiert. Die strukturierte Programmierung ermöglicht die Erstellung effizienterer Programme und erleichtert im Allgemeinen den Programmierern die Programmentwicklung.

Sie haben gelernt, dass eine Funktion aus einem Header und einem Rumpf besteht. Der Header enthält Informationen über Rückgabewert, Name und Parameter der Funktion. Der Rumpf enthält die Deklarationen der lokalen Variablen und die C- Anweisungen, die ausgeführt werden, wenn die Funktion aufgerufen wird. Außerdem wurde Ihnen gezeigt, dass lokale Variablen - das heißt, Variablen, die innerhalb einer Funktion deklariert wurden - völlig unabhängig von den anderen Programmvariablen sind, die woanders deklariert wurden.

Fragen und Antworten

Frage:
Kann es passieren, dass man mehr als einen Wert aus einer Funktion zurückgeben muss?

Antwort:
Häufig werden Sie mehr als einen Wert von einer Funktion zurückgeben müssen oder, was noch häufiger vorkommt, Sie wollen eine Wert, den Sie der Funktion übergeben haben, ändern und die Änderung nach dem Funktionsende beibehalten.

Frage:
Woher weiß ich, was ein guter Funktionsname ist?

Antwort:
Ein guter Funktionsname beschreibt knapp und prägnant, was die Funktion macht.

Frage:
Wenn Variablen im Listing vor main() deklariert werden, können Sie überall verwendet werden, während lokale Variablen nur in der speziellen Funktion verwendet werden können. Warum sollte man nicht einfach alles vor main() deklarieren?

Antwort:
Der Gültigkeitsbereich von Variablen wird noch ausführlich am Tag 11 besprochen. Dort werden Sie dann auch erfahren, warum es sinnvoller ist, Variablen lokal innerhalb von Funktionen zu deklarieren anstatt global vor main().

Frage:
Muss main() die erste Funktion in einem Programm sein?

Antwort:
Nein. Es ist allerdings in C üblich, dass die main()-Funktion die erste Funktion ist, die ausgeführt wird. Sie können sie jedoch irgendwo in Ihrer Quelltextdatei unterbringen. Die meisten Programmierer optieren für vorn oder hinten, damit man sie leichter findet.

Workshop

Der Workshop enthält Quizfragen, die Ihnen helfen sollen, Ihr Wissen zu festigen, sowie Übungen, die Sie anregen sollen, das Gelernte umzusetzen und eigene Erfahrungen zu sammeln. Die Lösungen zu den Fragen und den Übungen finden Sie in Anhang C.

Quiz

  1. Nutzt man bei der C-Programmierung die strukturierte Programmierung?
  2. Was verbirgt sich hinter dem Begriff »strukturierte Programmierung«?
  3. Im welchen Zusammenhang stehen C-Funktionen zur strukturierten Programmierung?
  4. Wie muss die erste Zeile einer Funktionsdefinition lauten und welche Informationen sollte sie enthalten?
  5. Wie viele Werte kann eine Funktion zurückgeben?
  6. Mit welchem Typ sollte eine Funktion deklariert werden, die keinen Rückgabewert hat?
  7. Was ist der Unterschied zwischen einer Funktionsdefinition und einem Funktionsprototyp?
  8. Was versteht man unter einer lokalen Variablen?
  9. Was ist das Besondere an den lokalen Variablen?
  10. Wo sollten Sie die Funktion main() definieren?

Übungen

  1. Schreiben Sie einen Header für eine Funktion namens tue_es(), die drei Argumente vom Typ char übernimmt und einen Wert vom Typ float an das aufrufende Programm zurückliefert.
  2. Schreiben Sie einen Header für eine Funktion namens eine_zahl_ausgeben(), die ein Argument vom Typ int übernimmt und keinen Wert an das aufrufende Programm zurückliefert.
  3. Welchen Typ haben die Rückgabewerte der folgenden Funktionen?
  4. a. int fehler_ausgeben ( float err_nbr);
  5. b. long datensatz_lesen ( int rec_nbr, int size );
  6. FEHLERSUCHE: Was ist falsch an folgendem Listing?
    #include <stdio.h>
    void print_msg( void );
    int main(void)
    {
    print_msg( "Diese Nachricht soll ausgegeben werden." );
    return 0;
    }
    void print_msg( void )
    {
    puts( "Diese Nachricht soll ausgegeben werden." );
    return 0;
    }
  7. FEHLERSUCHE: Was ist falsch an folgender Funktionsdefinition?
    int zweimal(int y);
    {
    return (2 * y);
    }
  8. Schreiben Sie Listing 4.5 so um, dass es nur eine return-Anweisung in der Funktion groesser_von() benötigt.
  9. Schreiben Sie eine Funktion, die zwei Zahlen als Argumente übernimmt und das Produkt der Zahlen zurückgibt.
  10. Schreiben Sie eine Funktion, die zwei Zahlen als Argumente übernimmt. Die Funktion sollte die erste Zahl durch die zweite teilen. Teilen Sie nicht, wenn die zweite Zahl Null ist. (Hinweis: Verwenden Sie eine if-Anweisung.)
  11. Schreiben Sie eine Funktion, die die Funktionen in den Übungen 7 und 8 aufruft.
  12. Schreiben Sie ein Programm, das eine Funktion verwendet, um den Mittelwert von fünf Werten vom Typ float zu ermitteln, die von dem Anwender eingegeben wurden.
  13. Schreiben Sie eine rekursive Funktion, die den Wert 3 um eine anzugebende Zahl potenziert. Wenn zum Beispiel 4 übergeben wird, gibt die Funktion den Wert 81 zurück.


vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbackKapitelanfangnächstes Kapitel


© Markt&Technik Verlag, ein Imprint der Pearson Education Deutschland GmbH