Anfang   zurück   weiter

Funktionen

Funktionen sind wesentlicher Bestandteil der C Programmierung und ermöglichen eine strukturierte Programmierung. Hier nun ein Beispiel einer einfachen Funktion:
/* Demonstriert den Gebrauch einer einfachen Funktion */
/* Berechnet die dritte Potenz einer ganzen Zahl */
#include <stdio.h>

/* Funktionsdeklaration */
long cubus(long x);

int main(void)
{
long eingabe, antwort;

 printf("\nGeben Sie eine positive ganze Zahl < 1291 ein: ");
 scanf("%ld", &eingabe);
 antwort=cubus(eingabe);
 printf("\nDie dritte Potenz von %ld ist %ld.\n", eingabe, antwort);
 return 0;
}

/* Funktionsdefinition */
long cubus(long x)
{
 long x_cubus;

 x_cubus = x * x * x;
 return x_cubus;
}

Vor dem Beginn des Hauptprogrammes stehen die Funktionsdeklarationen (Funktionsprototyp, function prototype), als ein Modell für die Funktion die später im Programm aufscheint. Eine Funktionsdeklaration enthält den Namen der Funktion, eine Liste von Variablen (inklusive ihrer Typen) die an sie übergeben werden müssen und den Typ der Variablen den die Funktion zurückgibt, falls ein Wert zurückgeliefert wird. Später im Programm folgt die eigentliche Funktionsdefinition.

Syntax:
Funktionsdeklaration:

return_type function_name(arg-type name-1, ... , arg-type name-n);
Funktionsdefinition:
return_type function_name(arg-type name-1, ... , arg-type name-n)
{
statements;
}

Die Funktionsdeklaration stellt dem Compiler die Beschreibung der Funktion zur Verfügung, die zu einem späteren Zeitpunkt im Programm definiert wird.

Die eigentliche Funktion ist in der Funktionsdefinition enthalten. Die Definition enthält den Code, der später ausgeführt wird. Die erste Zeile der Funktionsdefinition, der Funktionskopf (function header), ist bis auf das Semikolon mit der Zeile der Funktionsdeklaration ident (in der Funktionsdeklaration wäre es auch möglich die Variablennamen wegzulassen). Dem Funktionskopf folgt der Funktionskörper der, wie die Funktion main() in geschweifte Klammern eingeschlossen ist.

Funktionen und strukturierte Programmierung

Kleinere Programmieraufgaben lassen sich noch leicht innerhalb der Funktion main() realisieren, größere Projekte mit mehreren hundert oder tausend Anweisungen werden aber sinnvollerweise in kleinere Teile zerlegt programmiert. Bei der Planung eines größeren Programmes wird der sogenannte "Top-Down Approach" verwendet. In dieser Methode wird das Programm in kleine Subeinheiten zerteilt, die in der Form eines umgedrehten Baumes miteinander zusammenhängen. Dies wird für ein Adressverwaltungsprogramm im unteren Bild illustriert.

Programmstruktur

Die Hauptfunktion main() enthält nur noch wenig Code, sie besteht "hauptsächlich" aus Funktionsaufrufen. Komplexe Programmieraufgaben werden so mittels Funktionen in kleinere, einfachere Teile aufgespalten. Diese Teile werden bei der Programmerstellung "bottom up" erzeugt. Mit dieser Technik wird auch die Programmpflege erleichtert, da bei Änderungen nur einzelne Funktionen geändert zu werden brauchen.

Funktionen schreiben

Die erste Zeile einer Funktion ist der Funktionskopf, welcher aus drei Teilen besteht, wobei jede Komponente einem speziellen Zweck dient.

Rückgabetyp Funktionsname Parametrerliste
type funcname (param1, ...)

Der Funktionsrückgabetyp definiert den Datentyp den die Funktion an das aufrufende Programm zurückliefert. Das kann jeder beliebige C-Datentyp sein. Es lassen sich auch Funktionen definieren, die keinen Wert zurückliefern. Hier kommt der Datentyp void zum Einsatz.

int func1(...)    /* Rückgabewert vom Typ int */
double func1(...) /* Rückgabewert vom Typ double */
void func1(...)   /* liefert keinen Wert zurück */

Für den Funktionsnamen kann alles genommen werden, solange die C-Regeln der Namensgebung befolgt werden. Es ist eine gute Idee einen Namen zu wählen, der den Zweck der Funktion beschreibt.

Die Parameter Liste gibt an welche Argumente beim Aufruf an die Funktion übergeben werden. Dazu ist es notwendig, dass der Typ der Argumente der Funktion bekannt gemacht wird. Auch hier können alle C-Datentypen verwendet werden. Die Information über die Datentypen der Argumente wird im Funktionskopf durch die Parameterliste gegeben. Für jedes Argument das an die Funktion übergeben wird, muß in der Parameterliste ein Eintrag vorhanden sein. Der Eintrag spezifiziert den Typ und den Namen des Parameters.

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

spezifiziert eine Funktion ohne Rückgabewert mit drei Parametern: ein Typ int mit Namen x, ein Typ float mit Namen y und ein Typ char mit Namen z. Wird an die Funktion kein Parameter übergeben, sollte der Parameter void in der Parameterliste aufscheinen.

void xmpl2(void)

Die Funktionsdeklaration, die vor dem Beginn des Hauptprogrammes steht, unterscheidet sich von der Funktionsdefinition nur dadurch, dass die Funktionsdeklaration mit einem Semikolon beendet wird. Bei der Funktionsdeklaration ist es auch möglich in der Parameterliste die Parameternamen wegzulassen.

Unmittelbar an den Funktionskopf folgt der in geschweifte Klammern eingeschlossene Funktionskörper. Wird eine Funktion aufgerufen, so beginnt die Programmausführung mit der ersten auf die öffnende geschweifte Klammer folgenden Anweisung und endet mit einer return Anweisung oder mit der letzten Anweisung unmittelbar vor der schließenden geschweiften Klammer.

Lokale Variable

Innerhalb der Funktion können Variablen deklariert werden, die als lokale Variablen bezeichnet werden. Lokal bedeutet, dass die Variablen nur innerhalb der Funktion Gültigkeit besitzen und sich von anderen Variablen gleichen Namens, die anderswo im Programm deklariert wurden, unterscheiden.

/* locvar.c
   Demonstriert Gebrauch lokaler Variablen. */

#include <stdio.h>

int x = 1, y = 2;

void demo(void);

int main(void)
{
   printf("\nVor dem Aufruf von demo(), x = %d und y = %d.", x, y);
   demo();
   printf("\nNach dem Aufruf von demo(), x = %d und y = %d.", x, y);
   return 0;
  }

void demo(void)
{
/* Deklariere und initialisiere zwei lokale Variable. */
   int x = 88, y = 99;

/* Anzeige ihrer Werte. */
   printf("\nInnerhalb von demo(), x = %d und y = %d.", x, y);
}

Argumentübergabe

Die Argumente, die an die aufgerufene Funktion übergeben werden, sind in der Funktion nur als "Bilder" der Originalvariablen verfügbar. Das heißt, dass die Größen der Originale in der Funktion nicht veränderbar sind. Dies bezeichnet man als "call by value". Sollen die Werte der Originalvariablen veränderbar sein, müssen die Argumente als Referenzen ("call by reference") an die Funktion übergeben werden. Dies wird in C durch eine Übergabe von Zeigern auf die Originalvariablen erreicht. Sollen also die Originale verändert werden können, dürfen nicht die Namen der Variablen in der Parameterliste aufscheinen, sondern es müssen deren Adressen an die Funktion übergeben werden. In der Funktionsdeklaration sind die Parameter dementsprechend als Zeiger zu deklarieren.

/* swapdemo.c
   demonstriert call by reference */

#include <stdio.h>

void swap(int *a, int *b);

int main(void)
{
	int x = 13, y = 31;

	printf("Vor dem Aufruf von swap():  x = %d, y = %d\n", x, y);
	swap(&x, &y);
	printf("Nach dem Aufruf von swap(): x = %d, y = %d\n", x, y);
	return 0;
}

void swap(int *a, int *b)
{
	int c = *a;
	*a = *b;
	*b = c;
}

Übergabe von Arrays

Auch die Übergabe von Arrays an Funktionen bedarf einiger Vorbereitungen, da auch Arrays nur "by reference" übergeben werden können. Damit gibt es beim Aufruf keine Information über die Größe des übergebenen Feldes. Bei Zeichenketten, die ja in Form von Characterarrays angelegt werden, kann die Endkennung der Zeichenkette als Längeninformation dienen. Bei anderen Datentypen ist es sinnvoll die Arraygröße ergänzend als Parameter mitzugeben.

#include <stdio.h>

void str_ausgabe(char s[]);
void i_ausgabe(int a[], int size);

int main(void)
{
	char zk[81];
	int i_array[] = { 2, 4, 8, 16 };

	printf("Einen Satz bitte: ");
	gets(zk);
	str_ausgabe(zk);
	i_ausgabe(i_array, sizeof(i_array) / sizeof(int));
	return 0;
}

void str_ausgabe(char s[])
{
	int i;

	for(i = 0; s[i] != '\0'; i++) putchar(s[i]);
	putchar('\n');
}

void i_ausgabe(int a[], int size)
{
	int i;
	
	for(i = 0; i < size; i++) printf("%d ", a[i]);
	putchar('\n');
}

Werterückgabe

Jede Funktion kann maximal einen Wert direkt an das aufrufende Programm zurückliefern. Dies wird mittles der return Anweisung erreicht. Der unmittelbar auf die Anweisung return folgende Ausdruck wird als Rückgabewert an das aufrufende Programm zurückgegeben. Der Typ des retournierten Wertes ist jener, der in der Funktionsdeklaration angegeben wurde. Der Versuch aus einer Funktion, die keinen Rückgabewert liefern sollte, einen Wert zurückzugeben hat eine Compilerwarnung zur Folge. Ein aus einer Funktion zurückgelieferter Wert kann im aufrufenden Programm auch ignoriert werden. Soll mehr als ein Wert aus der Funktion zurückgeliefert werden, muß das über zusätzliche Parameter, die in Form von Zeigern übergeben werden, geschehen.

Verwendung von Kommandozeilenargumenten

Auch dem Hauptprogramm main() können beim Aufruf Argumente von der Kommandozeile übergeben werden. Das heißt beim Aufruf eines Programmes von der Eingabeaufforderung kann Information an das Programm weitergeleitet werden. Man kann also schreiben:
C:> progname arg1 arg2 ... argn
Um diese verschiedenen Argumente aus der Kommandozeile im Hauptprogramm main() verwenden zu können, muß main() folgend definiert werden:
int main(int argc, char *argv[])
{
/* Hier folgen die Anweisungen */
return 0;
}
Das erste, vom Betriebsystem an main() gelieferte, Argument argc ist eine Ganzzahl, die die Anzahl der Kommandozeilenargumente enthält. Dabei ist der Wert immer um eins größer als die Anzahl der Argumente, da der Programmname inklusive der Pfadangabe als erster Parameter übergeben wird. Der Parameter argv[] ist ein Array von Zeigern auf Strings. Die gültigen Subscripts für dieses Array sind also 0 bis argc - 1. Der erste Parameter von der Kommandozeile ist also in argv[1] enthalten. Die Trennung der Argumente von der Kommandozeile erfolgt durch Zwischenräme. Muß ein Argument einen Zwischenraum enthalten, so ist dieses Argument in Anführungszeichen einzuschließen.
C:> progname arg1 "arg zwei"
hat also arg1 als erstes Argument und das zweite Argument ist arg zwei. Die Verwendung von Kommandozeilenargumente zeigt das folgende Programm.
/* comm_arg.c
   Verwendung von Kommandozeilenargumenten. */

#include <stdio.h>

int main(int argc, char *argv[])
{
  int count;

  printf("Programm Name: %s\n", argv[0]);

  if (argc > 1)
  {
    for (count = 1; count < argc; count++)
      printf("Argument %d: %s\n", count, argv[count]);
  }
  else
    puts("Keine Kommandozeilenargumente eingegeben.");
  return 0;
}

Funktionen mit variabler Anzahl von Argumenten

Funktionen mit variabler Anzahl von Argumenten sind von den Bibliotheksfunktionen, wie z.B. printf() oder scanf(), vertraut. Es ist möglich solche Funktionen auch selbst zu programmieren. Programme, die Funktionen mit variabler Argumentliste aufweisen, müssen die Datei STDARG.H inkludieren.
Bei der Deklaration einer Funktion, die eine variable Argumentliste besitzt, müssen als erstes die fixen Parameter übergeben werden. Notwendig ist mindestens ein fixes Argument. Danach folgen am Ende der Parameterliste drei Punkte (...) um anzuzeigen, daß keines oder mehrere zusätzliche Argumente an die Funktion übergeben werden.
Der Zweck des (der) fixen Argumente(s) ist, der Funktion die Gesamtzahl und die Art der Argumente mitzuteilen. Als Beispiel denke man an printf(), wo die Zahl und Art der übergebenen Variablen im Formatstring spezifiziert wird.
In der Datei STDARG.H sind die "Tools" definiert, die zur Benutzung von Funktionen mit variabler Anzahl von Argumenten notwendig sind. Diese sind:

va_list, eine Zeigertype,

va_start(), ein Makro, das zur Initialiserung der Argumentliste verwendet wird,

va_arg(), ein Makro, um nacheinander die einzelnen Argumente aus der Variablenliste abzurufen.

va_end(), ein Makro, das schließlich "aufräumt", wenn alle Argumente aus der Variablenliste abgerufen worden sind.

Die Vorgangsweise zur Verwendung dieser Makros sei im folgenden skizziert:

  1. Man deklariere einen Zeiger vom Typ va_list. Dieser Zeiger wird zum Zugriff auf die individuellen Argumente verwendet. Es ist dabei üblich, aber nicht notwendig, diese Variable arg_ptr zu benennen.
  2. Man rufe das Makro va_start() auf und übergebe den Zeiger arg_ptr und den Namen des letzten fixen Argumentes. Das Makro va_start() hat keinen Rückgabewert, setzt aber den Zeiger arg_ptr auf die erste Variable in der variablen Argumentliste.
  3. Um die einzelnen Argumente abzurufen, wird va_arg() mit arg_ptr und dem Datentyp des nächsten Argumentes als Parameter aufgerufen. Der Rückgabewert von va_arg() ist der Wert des nächsten Argumentes.
  4. Wurden alle Daten aus der Variablenliste abgeholt, wird va_end() mit arg_ptr als Argument aufgerufen.

Hier nun ein Beispiel:

/* var_arg.c
   Funktion mit einer variablen Argumentliste. */

#include <stdio.h>
#include <stdarg.h>

float average(int num, ...);

int main(void)
{
  float x;

  x = average(10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
  printf("\nDer erste Mittelwert ist %f.", x);
  x = average(5, 101, 106, 96, 91, 95);
  printf("\nDer zweite Mittelwert ist %f.", x);

  return 0;
}

float average(int num, ...)
{
 va_list arg_ptr;  /* Deklariere eine Variable vom Typ va_list. */
 int count, total = 0;

 va_start(arg_ptr, num); /* Initialisiere den Argument Zeiger. */

/* Rufe jedes Argument aus der Variablenliste ab. */
 for (count = 0; count < num; count++)
  total += va_arg( arg_ptr, int );

 va_end(arg_ptr);  /* Aufraeumen */

 return ((float)total/num);
}


File I/O