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:
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.
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.
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 Typlong
benötigt und einen Wert vom Typlong
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 Funktionlong 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 Typlong
.
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 (hierkubik
) 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.
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.
rueckgabe_typ funktion_name( arg-typ name-1,...,arg-typ name-n);
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.
double quadriert( double zahl );
void bericht_ausgeben( int bericht_zahl );
int menue_option_einlesen( void );
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" );
}
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.
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.
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.
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.
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.
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 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.
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.
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.
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.
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.
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.
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 mehrerenreturn
-Anweisungen viel einfacher zu lesen und zu warten. In solchen Fällen sollte die gute Wartbarkeit Vorrang haben.
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.
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);
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 Typvoid
als Ausdruck zu verwenden, erzeugt der Compiler
eine Fehlermeldung.
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.
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«.
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.
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.
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.
main()
definieren?
tue_es()
, die drei
Argumente vom Typ char
übernimmt und einen Wert vom Typ float
an das
aufrufende Programm zurückliefert.
eine_zahl_ausgeben()
, die
ein Argument vom Typ int
übernimmt und keinen Wert an das aufrufende
Programm zurückliefert.
int fehler_ausgeben ( float err_nbr);
long datensatz_lesen ( int rec_nbr, int size );
#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;
}
int zweimal(int y);
{
return (2 * y);
}
return
-Anweisung in der
Funktion groesser_von()
benötigt.
if
-Anweisung.)
float
zu ermitteln, die von dem Anwender eingegeben
wurden.
3
um eine anzugebende Zahl
potenziert. Wenn zum Beispiel 4
übergeben wird, gibt die Funktion den Wert 81
zurück.