Funktionen

Einleitung

Werden in einem Programm mehrmals die gleichen Operationen ausgeführt, so kann es vorkommen, dass der Quelltext nach einer gewissen Länge unübersichtlich wird. Hinzu kommt noch, dass die Fehleranfälligkeit steigt. Mithilfe von sogenannten Unterprogrammen bzw. Subroutinen, lässt sich dieses Problem verhindern. In C gibt es diese in Form von Funktionen. Arbeitsschritte, die mehrmals vorkommen sollen, werden in eine Funktion geschrieben. An den Stellen im Programm, an denen diese Arbeitsschritte ausgeführt werden sollen, wird die jeweilige Funktion aufgerufen. Das kürzt den Quelltext erheblich und macht ihn übersichtlicher. Funktionen haben wir schon mehr oder weniger unbewusst genutzt. Da wäre die Hauptfunktion main() und die Funktionen der Standardbibliothek, wie z.B. printf().

Deklaration und Aufruf

Funktionen werden folgendermaßen deklariert:

  [Rückgabetyp] [Name_der_Funktion]([Parameter])
  {
    /* Code */
  }

Das Beispiel zeigt ein Programm, das mit Hilfe einer Funktion den Text „Hello World“ ausgibt.

  #include <stdio.h>
 
  void helloWorld()  /* Deklaration der Funktion */
  {
    printf("Hello World"); /* Dieser Code wird immer ausgeführt,
                              wenn die Funktion aufgerufen wird */
  }
 
  int main()
  {
    helloWorld();  /* Aufruf der Funktion */
    return 0;
  }
 

Der Datentyp ist in diesem Beispiel void und es werden keine Parameter angegeben. Streng genommen wäre dieses Beispiel eine Prozedur, weil hier der Rückgabewert vom Typ void ist. Einige Programmiersprachen, wie z.B. Pascal oder Delphi, unterscheiden explizit zwischen Prozeduren und Funktionen. In C werden diese aber gleichgesetzt. Was das alles genau bedeutet, ist im Moment noch unwichtig und wird später erklärt.

Wichtig ist aber, dass die Klammern immer mit eingegeben werden müssen, da der Compiler ansonsten eine Fehlermeldung ausgibt. Noch ist auch zu beachten, dass jede Funktion vor main() deklariert werden muss. Das lässt sich ändern aber dazu kommen wir später.

Exkurs: Lokale und globale Variablen

In jeder Funktion lassen sich auch Variablen deklarieren. Hier ein Beispiel:

  #include <stdio.h>
 
  void example()
  {
    int x;
  }
 
  int main()
  {
    int x;
    example();
    /* weitere Anweisungen */
    return 0;
  }

Diese Möglichkeit ist in der Tat erlaubt. Natürlich ist es verboten, Variablen mit dem gleichen Namen in einer Funktion zu deklarieren. Nur wie kann das sein? Die Variable x in der Funktion example() ist nur darin gültig. Sie ist für main() im Prinzip 'unbekannt'. Wird eine Variable deklariert, so reserviert das Programm für diese Variable Speicherplatz im Hauptspeicher. Wird eine Funktion beendet, so wird auch der Speicherplatz, der für die Variable reserviert wurde, freigegeben. Die Variable wird dadurch im Prinzip mit ihrem Wert zerstört. Wird die Funktion wieder aufgerufen, so wird wieder Speicherplatz für die Variable reserviert und beim Beenden der Funktion wieder freigegeben. All diese Variablen sind sogenannte lokale Variablen. Bisher haben wir auch nur damit gearbeitet. Es gibt aber auch globale Variablen, die zu Beginn des Programms deklariert und erst zum Ende des jeweiligen Programms zerstört werden. Ein Beispiel:

  #include <stdio.h>
 
  long x; /* Deklaration einer globalen Variable */
 
  void example()
  {
    x = 12;  
  }
 
  int main()
  {
    example();  /* x hat jetzt den Wert 12 */
    x = 4;  /* Nun hat x den Wert 4 */
    return 0;
  }
 

Hier sieht man nochmals deutlich, dass eine Funktion zuerst aufgerufen werden muss, bevor die Anweisungen, die in ihr stehen, ausgeführt werden. Es ist erlaubt, eine lokale Variable mit dem gleichen Namen einer globalen Variablen zu deklarieren. Hier ein Beispiel:

  #include <stdio.h>
 
  long x;
 
  int main()
  {
    double x;
    return 0;
  }

Auch diese Möglichkeit ist erlaubt. Die globale Variable x wurde durch die lokale Variable x verdeckt. Für double x wurde weiterer Speicherplatz zur Verfügung gestellt. Die Hauptfunktion kann nicht mehr darauf zugreifen. Diese Methode ist eine beliebte Fehlerquelle und deswegen ist davon abzuraten. Allgemein sollte man, solange es möglich ist, auf die Nutzung von globalen Variablen verzichten, da man schnell den Überblick über die verschiedenen Zugriffe verliert.

Parameter

Das erste Beispiel einer Funktion, welches nur den Text „Hallo Welt“ ausgibt, ist nicht besonders nützlich. Soll solch eine Art von Unterprogramm mit verschiedenen Werten rechnen, so müsste man auf globale Variablen zugreifen. Man soll es aber vermeiden. Wie soll das gehen? Man übergibt der Funktion die Werte mithilfe von Parametern. In dem folgenden Beispiel ist eine Funktion, deren Parameter durch zwei geteilt wird:

  #include<stdio.h>
 
  void div(long value) /* Deklaration der Parameter */
  {
    value /= 2;
    printf("%i",value);
  }
 
  int main()
  {
    long example = 5;
    div(example);  /* Aufruf der Funktion mit Parameterübergabe */
  }

Im Kopf der Funktion div() wird die Variable value vom Typ long deklariert. Weil das ein Parameter ist, wird diese Variable nicht im Block deklariert. Initialisiert wird die Variable value durch den Aufruf der Funktion div() in main(). Ihr wird der Wert von example übergeben. Hier ist zu sehen, dass die Variable example einen anderen Name hat als der Parameter von div(). Das ist auch egal, weil div() eine völlig andere Variable, also value, deklariert. Es muss aber darauf geachtet werden, dass die Datentypen der übergebenen Variablen und des Parameters identisch sind. Demnach dürfen auch keine float-Variablen an einen double-Parameter übergeben werden. Bisher haben die Beispielfunktionen nur einen Parameter erhalten. Natürlich kann man einer Funktion mehrere Parameter übergeben. Diese dürfen auch unterschiedliche Datentypen enthalten. Im Funktionskopf werden die einzelnen Parameter durch Kommata getrennt. Das selbe gilt auch für den Aufruf, bzw. die Parameterübergabe. Dazu ein Beispiel:

  #include<stdio.h>
 
  void showSum(short a, short b)  /* Deklaration von zwei Parametern */
  {
    printf("%i + %i = %i", a, b, (a + b));
  }
 
  int main()
  {
    short a = 4;
    short b = 13;
    showSum(a, b);  /* Übergabe von zwei Werten */
  } 

Rückgabewerte

Bisher haben wir, wie schon erwähnt, nur mit Prozeduren und, wenn man es streng nimmt, nicht mit Funktionen gearbeitet. Wo ist denn da der Unterschied? Die Antwort ist, dass eine Prozedur keine Rückgabewerte hat. Eine Funktion dagegen schon. Außerdem wird eine Prozedur einfach nur aufgerufen, eine Funktion dagegen wird einer Variablen zugewiesen. Wenn man sich das letzte Beispiel anschaut, sieht man, dass die Funktion div() den Wert des Parameters ausgibt. Möchte man aber die Ausgabe durch printf() vermeiden und den Wert nur verändern lassen, um mit ihm beispielsweise weiterzurechnen, so muss man auf Rückgabewerte zugreifen. Bisher haben wir die Funktionen nur mit dem Datentyp void deklariert. Alle anderen Datentypen sind aber auch zulässig. Würde man der Funktion div() z.B. den Datentyp long zuweisen, so gäbe es einen Compilerfehler. Die Funktion div() müsste nämlich einen Wert zurückgeben und der Aufruf müsste anders aussehen. In dem folgenden Beispiel wird aus div eine „wahre“ Funktion gemacht:

  #include <stdio.h>
 
  long div(long value) 
  {
    value /= 2;
    return value;  /* Wertrückgabe */
  }
 
  int main()
  {
    long example, result;
    example = 3;
    result = div(example);  /* Zuweisung des Ergebnisses von div() an result */
    return 0;
  }
 

Das Schlüsselwort für die Rückgabe von Werten lautet return. Wir haben es schon in main() benutzt. Zu beachten ist, dass eine Funktion nur einen Wert zurückgeben darf. Man darf aber mehrere return-Anweisungen in einer Funktion schreiben, solange jede sich in einer eigenen if-Abfrage oder in einem eigenen case einer switch-Anweisung befinden. Die Anweisung return bedeutet für den Compiler, dass die Funktion danach beendet ist. In main() wurde der Variablen result der Rückgabewert von div() zugewiesen. Man muss noch beachten, dass der Datentyp der Funktion mit dem Datentyp des Rückgabewertes identisch sein muss. Daraus folgt, dass der Datentyp der Funktion den Datentypen des Rückgabewertes definiert.

Prototypen

Bisher wurden alle Funktionen vor main() definiert. Der Compiler verarbeitet den Quelltext von oben nach unten. Jede Funktion, die nach main() erscheint, wird vom Compiler ignoriert und es gibt so auch keine Probleme. Wird aber die Funktion in main() aufgerufen, so entsteht ein Fehler, weil der Compiler die Funktion nicht zuordnen kann. Es geht auch anders. Dazu muss man sogenannte Funktionsprototypen definieren. Mithilfe dieser Prototypen kann der Compiler Funktionen, die nach main() definiert wurden, überprüfen, ob deren Parameter richtig übergeben wurden. Ein Beispiel:

  #include<stdio.h>
 
  int example(float value);  /* Prototypen */
  void foo();  
 
  int main()
  {
    float value = 4;
    int sth = example(value);
    foo();
    return 0;
  }
 
  int example(float value)
  {
    /* Code */
  }
 
  void foo()
  {
    /* Code */
  }

Prototypen sind nichts anderes, als Funktionsköpfe ohne Inhalt, die vor main() definiert werden. Eine Sache ist aber zu beachten: Die Prototypen müssen durch ein Semikolon abgeschlossen werden. Es müssen sogar noch nicht einmal die Parameter in die Prototypen eingegeben werden. Man kann auch einfach nur die Datentypen der Parameter, ohne ihre Namen, hineinschreiben. Diese Variante ist zwar zulässig, davon ist aber, aufgrund der Übersicht, strikt abzuraten. Prototypen haben noch einen weiteren Vorteil: Sie bieten eine grobe Übersicht über alle Funktionen im Programm. Deswegen sollte man immer, auch wenn es nicht notwendig ist, Prototypen einsetzen.


Diskusionthread