6 Funktionen
6.1 Aufbau einer Funktion
6.2 Datentyp und Deklaration einer Funktion
6.3 Parameterübergabe
6.4 Speicherklassen und Geltungsbereiche von Namen
6.5 Rekursive Funktionen
- Funktionen sind Hilfsmittel, um Problemstellungen in kleine
Teilprobleme zerlegen zu können
- sie dienen damit einer strukturierten Programmierung
- typische Anwendung für Funktionen ist weiterhin die Erledigung
immer wiederkehrender Aufgaben
- für die wichtigsten Aufgaben gibt es bereits Funktionen,
sie sind in der C-Programmbibliothek enthalten
- für eigene Zwecke kann man natürlich auch eigene
Funktionen schreiben
- C Programme bestehen typischerweise aus vielen kleinen und nicht
aus wenigen großen Funktionen
6.1 Aufbau einer Funktion
- eine Funktion hat einen festgelegten Aufbau, der wie folgt aussieht:
Typ name(Parameterliste)
{
Vereinbarungen
Anweisungen
return Funktionswert, optional
}
|
- die runden Klammern müssen stehen, damit name zur
Funktion wird (Zwischenraum zwischen name und (
ist erlaubt
- die Parameterliste in den runden Klammern ist optional, d.h.
sie muß nur vorhanden sein, wenn der Funktion wirklich Parameter
übergeben werden. Andernfalls ist als Platzhalter void anzugeben
- die Parameterliste enthält sowohl die Namen als auch die
Typdeklarationen der Parameter
- die Gesamtheit der Vereinbarungen und Anweisungen der Funktion
selbst nennt man den Funktionskörper, er muß durch
geschweifte Klammern eingeschlossen sein
- die return-Anweisung darf an beliebiger Stelle im Funktionskörper
stehen, mit ihr erfolgt der Rücksprung in die aufrufende
Funktion
- die return-Anweisung kann auch ganz fehlen, dann erfolgt
der Rücksprung in die aufrufende Funktion beim Erreichen
des Funktionsendes (der schließenden geschweiften Klammer
um den Funktionkörper)
- der Funktionswert hinter der return-Anweisung wird meist
in runde Klammern eingeschlossen. Dies ist aber nicht notwendig.
Fehlt der Funktionswert, so wird an die aufrufende Funktion auch
kein Wert zurückgegeben (siehe void-Datentyp für
Funktionen)
- ebenfalls erlaubt und bei älteren C-Programmen (nicht ANSI-C)
häufig zu finden ist folgender Funktionsaufbau:
Typ name(Parameternamen, optional)
Parameterdeklarationen, optional
{
Vereinbarungen
Anweisungen
return Funktionswert,optional
}
|
- die Parameternamen müssen alle in zugehörigen Parameterdeklarationen
auftauchen
- die einfachste Funktion ist die leere Funktion:
Beispielprogramm p6-1.c:
/*
* p6-1.c
* Beispielprogramm 1, Abschnitt 6
* Alle Zeilen mit Suchmuster finden
*/
#include <stdio.h>
#define MAXLINE 1000
int getline(char [], int);
int index(char [], char []);
void main(void)
{
char line[MAXLINE];
while (getline(line, MAXLINE - 1) > 0)
if (index(line, "the") > 0)
printf("%s", line);
} /* main() */
/* Zeile in s ablegen, Länge liefern */
int getline(char s[], int lim)
{
int c, i;
i = 0;
while (--lim > 0 && (c = getchar()) != EOF && c != '\n')
s[i++] = c;
if (c == '\n')
s[i++] = c;
s[i] = '\0';
return(i);
} /* getline() */
/* Position von t in s liefern, -1 falls nicht da */
int index (char s[], char t[])
{
int i, j, k;
for (i = 0; s[i] != '\0'; i++) {
for (j = i, k = 0; t[k] != '\0' && s[j] == t[k]; j++, k++)
;
if (t[k] == '\0')
return(i);
}
return(-1);
} /* index() */
|
6.2 Datentyp und Deklaration einer Funktion
- bisher haben wir nur int Funktionen besprochen
- Funktionsaufrufe und Funktionen ohne Typangabe werden wie int
Funktionen behandelt
- da char immer in int gewandelt wird, sind damit
auch schon die char Funktionen abgedeckt
- die meisten Funktionen sind int Funktionen
- soll eine Funktion einen anderen Datentyp als int liefern,
so müssen 2 Dinge getan werden:
- die Funktion muß vor ihrem ersten Aufruf als eine
solche mit dem gewünschten Typ deklariert werden
- der Funktionsdefinition selbst muß der Typ vorangestellt
werden
Beispielprogramm p6-2.c
/*
* p6-2.c
* Beispielprogramm 2, Abschnitt 6
* Umfang und Flaeche eines Kreises
*/
#include <stdio.h>
#define PI 3.1415
void main(void)
{
int i;
float radius;
float umfang(float);
double flaeche(float);
for (i = 1, radius = 1.; i <= 10; i++, radius += .5)
printf("Kreis %2d Radius=%6.2f Umfang=%8.4f Flaeche=\
%8.4f\n", i, radius, umfang(radius), flaeche(radius));
} /* main() */
float umfang(float rad)
{
return(2. * PI * rad);
} /* umfang() */
double flaeche(float r)
{
return(PI * r * r);
} /* flaeche() */
|
Beispielprogramm p6-3.c
/*
* p6-3.c
* Beispielprogramm 3, Abschnitt 6
* Zeichenkette nach double wandeln
*/
double atof(char s[])
{
double val, power;
int i, sign;
/* Zwischenraeume uebergehen */
for (i = 0; s[i] == ' ' || s[i] == '\n' || s[i] == '\t`; i++)
;
sign = 1;
if (s[i] == '+' || s[i] == '-') /* Vorzeichen */
sign = (s[i++] == '+') ? 1 : -1;
for (val = 0; s[i] >= '0' && s[i] <= '9'; i++)
val = 10 * val + s[i] - '0';
if s[i] == '.')
i++;
for (power = 1, s[i] >= '0' && s[i] <= '9'; i++) {
val = 10 * val + s[i] - '0';
power *= 10;
}
return (sign * val / power);
} /* atof() */
|
- Funktionen haben oft Wirkungen, die über den Funktionswert
hinausgehen (z.B. getline: Füllen eines Zeichenvektors)
- der Funktionswert dient in diesen Fällen dann häufig
nur als Statusanzeige
- der in der Funktion ermittelte Funktionswert muß in der
aufrufenden Funktion nicht ausgewertet, also auch nicht zugewiesen
werden
- soll eine Funktion grundsätzlich keinen Funktionswert liefern,
dann sollte man sie mit dem speziellen Bezeichner void deklarieren;
die Funktion entspricht dann einer SUBROUTINE in FORTRAN bzw.
einer PROCEDURE in PASCAL
Beispielprogramm p6-4.c
/*
* p6-4.c
* Beispielprogramm 4, Abschnitt 6
* Ausgabe eines Hilfetextes
*/
#include <stdio.h>
void help(int nr)
{
switch (nr) {
case 1:
printf("Hilfe 1\n");
break;
case 2:
printf("Hilfe 2\n");
break;
case 3:
printf("Hilfe 3\n");
break;
default:
printf("Fehlerhafter Aufruf von HELP\n");
}
} /* help() */
|
- in ANSI-C sind bei einer Funktionsdeklaration außer dem Typ der
Funktion auch die Typen der Parameter (ohne ihre Namen) in der Parameterliste
anzugeben (sog. Prototyping):
float umfang(float);
double flaeche(float);
int funcbei(int, float, double);
void proc(void);
|
- nach einer solchen Deklaration der Parametertypen sind z.B.
der ANSI C-Compiler oder das Prüfprogramm lint
in der Lage, bei jedem Funktionsaufruf die richtigen Typen der
verwendeten aktuellen Parameter sowie deren Anzahl abzuprüfen.
Eine parameterlose Funktion wird mit dem Platzhalter void
in der Parameterliste gekennzeichnet
6.3 Parameterübergabe
- alle Parameter werden "by value" übergeben,
d.h. eine Kopie des Parameterwertes wird übergeben
- das bedeutet, daß Funktionen ihre Parameter nur innerhalb
der Funktion, aber nicht im aufrufenden Programm ändern können
Beispielprogramm p6-5.c
/*
* p6-5.c
* Beispielprogramm 5, Abschnitt 6
* Beispiel fuer Parameteruebergabe und die
* Aenderbarkeit von Parametern in Funktionen
*/
#include <stdio.h>
void main(void)
{
float radius, r;
int i;
float neurad(int, float);
i = 10;
radius = 5.23;
printf("Vor Aufruf:\t\tI=%2d\tRADIUS=%6.2f\n", i, radius);
r = neurad(i, radius);
printf("Nach dem Aufruf:\tI=%2d\tRADIUS=%6.2f\n", i, radius);
printf("Aber Funktionswert:\t\tR= %6.2f\n", r);
} /* main() */
float neurad(int i, float rad)
{
i = 0;
rad = rad * 2;
printf("In der Funktion:\tI=%2d\tRADIUS=%6.2f\n", i, rad);
return (rad);
} /* neurad() */
|
- der Lauf des Programm führt zu folgender Ausgabe:
Vor Aufruf: I=10 RADIUS= 5.23
In der Funktion: I= 0 RADIUS= 10.46
Nach dem Aufruf: I=10 RADIUS= 5.23
Aber Funktionswert: R= 10.46
|
- sollen Parameter in der Funktion dauerhaft geändert werden
können, so müssen Adressen der zu ändernden
Objekte übergeben werden
- wird ein Feld (oder Vektor) als Parameter übergeben, so
wird die Adresse des Feldes, genauer die Adresse des ersten Elementes
des Feldes übergeben (deshalb funktioniert z.B. strcat
(siehe Beispielprogramm p4-2.c))
Beispielprogramm P6-6.c
/*
* p6-6.c
* Beispielprogramm 6, Abschnitt 6
* Beispiel fuer Parameteruebergabe und die
* Aenderbarkeit von Parametern in Funktionen
* Anwendung von Pointern
*/
#include <stdio.h>
void main(void)
{
float radius, r;
int i;
float neurad(int *, float *);
i = 10;
radius = 5.23;
printf("Vor Aufruf:\t\tI=%2d\tRADIUS=%6.2f\n", i, radius);
r = neurad(&i, &radius);
printf("Nach dem Aufruf:\tI=%2d\tRADIUS=%6.2f\n", i, radius);
printf("Aber Funktionswert:\t\tR=%6.2f\n", r);
} /* main() */
float neurad(int *i, float *rad)
{
*i = 0;
*rad = *rad * 2;
printf("In der Funktion:\tI=%2d\tRADIUS=%6.2f\n", *i, *rad);
return (*rad);
} /* neurad() */
|
- der Programmlauf führt zu folgender Ausgabe:
Vor Aufruf: I=10 RADIUS= 5.23
In der Funktion: I= 0 RADIUS= 10.46
Nach dem Aufruf: I= 0 RADIUS= 10.46
Aber Funktionswert: R= 10.46
|
6.4 Speicherklassen und Geltungsbereiche von Namen
- wir haben bisher an verschiedenen Stellen schon einmal die 4
Speicherklassen extern, auto, static und
register flüchtig kennengelernt
- im Zusammenhang mit Funktionen ist jetzt ein guter Zeitpunkt,
diese Kenntnisse zu vertiefen
1. Speicherklasse auto
- alle Variablen, denen nicht explizit eine Speicherklasse zugewiesen
wird und die nicht außerhalb von Funktionen vereinbart werden,
fallen in die Speicherklasse auto
- man kann einer Variablen explizit die Speicherklasse auto
zuordnen, indem man vor die Typangabe bei der Variablenvereinbarung
das Schlüsselwort auto setzt
- automatische Variable werden bei jedem Funktionsaufruf neu erzeugt
und beim Verlassen der Funktion wieder zerstört
- daher ist ihr Geltungsbereich auf die lokale Funktion, in der
sie vereinbart wurden, beschränkt
- dies gilt auch für Blöcke (Variablen, die innerhalb
von Blöcken vereinbart werden und die in die Speicherklasse
auto fallen, sind nur lokal in diesem Block bekannt)
- auto Variablen können beliebig (nicht nur mit Konstanten)
initialisiert werden
- nicht initialisierte auto Variablen haben einen undefinierten
Wert (es existiert kein Weglasswert!)
- auto-Vektoren können nicht initialisiert
werden
2. Speicherklasse extern
- alle Objekte, die außerhalb von Funktionen vereinbart
werden, sind in der Speicherklasse extern
- die Funktionsnamen selbst sind ebenfalls in der Speicherklasse
extern
- externe Objekte sind ab ihrer Vereinbarung bis zum Ende des
Quellencodes und sogar in anderen Quellencodedateien bekannt
- bei der Vereinbarung eines Objektes als extern kann es sich
um eine Definition oder um eine Deklaration handeln
- die folgenden Vereinbarungen (immer außerhalb von Funktionen)
unterscheiden sich wesentlich:
int sp = 0; /* Definition */
double vector[N];
extern int sp; /* Deklaration */
extern double vector[];
|
- bei einer Definition wird eine Variable erzeugt
- nur hier ist eine Initialisierung möglich
- fehlt eine Initialisierung, so wird der Weglasswert 0 eingesetzt
- bei einer Deklaration werden nur die Variableneigenschaften
festgelegt, die Variable selbst muß an anderer Stelle im
Quellencode definiert sein
- Deklarationen dürfen für eine bestimmte Variable mehrmals
im (gesamten) Quellencode vorkommen, eine Definition aber nur einmal
Beispielprogramm p6-7.c
/*
* p6-7.c
* Beispielprogramm 7, Abschnitt 6
*/
#include <stdio.h>
#define BUFSIZE 100
char buff[BUFSIZE]; /* Puffer fuer ungetch() */
int bufp = 0; /* naechste freie Position */
/* (evtl. zurueckgest.) Zeichen holen */
int getch()
{
return ((bufp > 0) ? buff[--bufp] : getchar());
} /* getch() */
/* Zeichen zurueckstellen */
int ungetch(int c)
{
if (bufp > BUFSIZE)
printf("ungetch: Zuviele Zeichen\n");
else
buff[bufp++] = c;
} /* ungetch() */
|
3. Speicherklasse static
- static Objekte können sowohl intern als auch extern
sein
- static Objekte innerhalb von Funktionen sind nur lokal
bekannt, behalten im Gegensatz zu auto Objekten aber ihre
Werte zwischen den Funktionsaufrufen bei
- bzgl. der Initialisierung gilt dasselbe wie für externe
Objekte, static Vektoren sind daher initialisierbar
- Zeichenketten innerhalb von Funktionen sind immer in der Speicherklasse
static (z.B. printf()-Parameterstring)
- static Objekte außerhalb von Funktionen sind externe
Objekte, deren Namen aber nur in dieser Quellcodedatei bekannt ist
- Beispiele zur Initialisierung eines Vektors:
static int ndigit[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
char string[] = "Dies ist ein String";
|
4. Speicherklasse register
- in dieser Speicherklasse können sich einfache Variable befinden
- sie entspricht in ihren sonstigen Eigenschaften der Speicherklasse auto
- der Compiler versucht register Variablen so zu verwenden,
daß sie in einem wirklichen Hardwareregister der CPU gehalten werden
- ist dies nicht möglich, so wird register ignoriert
und die Variable wie auto behandelt
6.5 Rekursive Funktionen
- Funktionen dürfen zwar nicht geschachtelt sein, aber sie
können sich selbst direkt oder indirekt aufrufen
- bei jedem Funktionsaufruf werden neue eigene lokale Variablen
erzeugt (außer bei der Speicherklasse static)
- viele Probleme lassen sich kompakt und elegant mittels Rekursion
lösen
Beispielprogramm p6-8.c
/*
* p6-8.c
* Beispielprogramm 8, Abschnitt 6
* n dezimal ausgeben
* Ohne Rekursion
*/
#include <stdio.h>
void printd(int n)
{
char s[10];
int i;
/* Vorzeichen ausgeben */
if (n < 0) {
putchar('-');
n = -n;
}
/* naechstes Zeichen holen und durch Division entfernen */
i = 0;
do {
s[i++] = n % 10 + '0';
} while ((n /= 10) > 0);
/* in umgekehrter Reihenfolge ausgeben */
while (--i >= 0)
putchar(s[i]);
} /* printd() */
|
Beispielprogramm p6-9.c
/*
* p6-9.c
* Beispielprogramm 9, Abschnitt 6
* n dezimal ausgeben
* Mit Rekursion
*/
#include <stdio.h>
void printd(int n)
{
int i;
/* Vorzeichen */
if (n < 0) {
putchar('-');
n = -n;
}
/* printd rekursiv aufrufen */
if ((i = n / 10) != 0)
printd(i);
putchar(n % 10 + '0');
} /* printd() */
|
Beispielprogramm p6-10.c
/*
* p6-10.c
* Beispielprogramm 10, Abschnitt 6
* Fakultaet berechnen
*/
long fak(int n)
{
if (n <= 1)
return(1);
else
return(n * fak(n - 1));
} /* fak() */
|
zurück zum Inhaltsverzeichnis