Syntax von Funktionen

In C und C++ wird eine Funktion im Programmcode geschrieben, indem ein Rückgabetyp, ein Symbol und eine Auflistung von Parametern deklariert wird. Durch eine Deklaration der Funktion kann sie an anderer Stelle mittels des Funktionsaufruf-Operators aufgerufen werden.

float area(float r);

Um eine Funktion auszuprogrammieren, wird sie mittels Programmcode innerhalb geschweifter Klammern {} ausformuliert.

float area(float r){
  return r*r;
}

Details

Hier auf dieser Seite wird explizit die Syntax und Semantik von Funktionen beschrieben. Für eine allgemeine Übersicht über das Thema Deklaration und Definition wird auf die entsprechende Seite bei den Konzepten verwiesen.

Eine Funktions-Deklaration wird durch ein Semikolon ; abgeschlossen. Die Funktions-Definition, also die tatsächliche Ausprogrammierung der Funktion verwendet anstelle des Semikolons geschweifte Klammern {}.

Eine Funktions-Deklaration kann bei gewissen Begebenheiten auch als Prototyp bezeichnet werden.

Der Rückgabetyp in Verbindung mit der Parameterliste wird als Funktionstyp oder Signatur bezeichnet. Ein eindeutiger Funktionstyp besteht somit stets aus dem Rückgabetyp UND der Parameterliste. Weitere Erklärungen dazu können bei den Funktions-Pointern nachgelesen werden.

Rückgabetyp

Der Rückgabetyp einer Funktion steht an erster Stelle der Funktionsdeklaration und legt den Typ des Wertes fest, welcher durch die Funktion zurückgegeben wird.

float area(float r);

Gibt die Funktion keinen Wert zurück, so handelt es sich um eine sogenannte Prozedur. Eine Prozedur unterscheidet sich von einer Funktion somit einzig darin, dass eine Funktion einen Wert zurück gibt, eine Prozedur jedoch nicht. In C und C++ wird deswegen der Begriff Prozedur nicht verwendet, sondern als Funktion mit dem Rückgabetyp void angegeben, was für den Rückgabewert zu interpretieren ist als: ungültig, zu ignorieren.

void print_addition(int a, int b);

Beim Versuch, bei einer Funktion mit Rückgabetyp void trotzdem den Wert im Programm zu verwenden, wird der Compiler einen Fehler wie etwa den folgenden ausgeben: void value not ignored as it ought to be. Beim Versuch, in einer solchen Funktion trotz der Angabe, dass kein Wert zurückgegeben werden soll, einen Wert zurückzugeben, wird eine Warnung wie die folgende ausgegeben: return with a value, in function returning void.

Symbol

Einer Funktion kann ein Symbol zugewiesen werden, oder anders gesagt ihr ein Name gegeben werden. Bei der Funktionsdeklaration wird der Name vor den Klammern der Parameterliste angegeben:

float area(float r);

Parameterliste

Jede Funktion kann eine beliebige Anzahl an Parametern deklarieren, welche die bei einem Funktionsaufruf übergebenen Argumente speichern. Die einzelnen Parameter stehen später innerhalb der Funktion als normale Variablen zur Verfügung. Parameter werden als sogenannte Parameterliste in runden Klammern () nach dem Funktionsnamen angegeben.

int   do_nothing1    ();
int   do_nothing2    (void);
float area           (float r);
void  print_addition (int a, int b);
int   printf         (const char *restrict format, ...);

Es sei zu beachten, dass andere Quellen die Parameterliste als Argumentenliste bezeichnen. Hier auf ManderC wird bei der Signatur einer Funktion stets von einer Parameterliste gesprochen, da es sich um die Deklaration der innerhalb der Funktion verfügbaren Variablen handelt. Für weitere Informationen über den Unterschied zwischen Parameter und Argumente sowie die verschiedenen Übergabemechanismen wird auf die Argumente- und Parameter-Seite verwiesesen.

Die einzelnen Parameter entsprechen grundsätzlich Variablendeklarationen (mit Typ und Name) und werden mittels Komma , voneinander getrennt. Es ist zu beachten, dass es sich hierbei NICHT um den Sequenz-Operator handelt, sondern um eine einfache Auflistung mittels Komma. Eine Parameterliste mit mindestens einem Parameter kann auch mittels der Auslassungspunkten ... erweitert werden, was eine beliebige zusätzliche Anzahl an zu übergebender Parameter erlaubt.

Wenn eine leere Parameterliste angegeben wird, bedeutet dies unter C++, dass die Funktion keine Parameter erwartet. In C bedeutet eine leere Parameterliste, dass nicht spezifiziert ist, wieviele Parameter erwartet sind. Eine Funktion, die explizit keine Parameter annehmen soll, kann (in C und C++) mittels dem void-Keyword als einzigen Parameter innerhalb der Klammern angegeben werden.

Wenn in C bei der Funktionsdeklaration die ganze Parameterliste leer ist (auch kein void), so wird das Symbol lediglich als eine Funktion deklariert, jedoch nicht spezifiziert, wieviele Parameter sie tatsächlich annimmt. Erst ab Auftreten der Definition der Funktion wird die Parameterliste festgelegt. Dies kann bei unsorgfältiger Programmierung zu Fehlern führen. Im folgenden Beispiel wird die Funktion ohne Parameterliste deklariert und kann somit mit beliebigen Argumenten aufgerufen werden, solange sie noch nicht definiert wurde. Dadurch entstehen fehlerhafte Ausgaben. In C++ funktioniert dies jedoch nicht mehr.






1234
and now...
8094
#include <stdio.h>

void printInt();

int main(){
  printInt(1234);
  printf("and now...\n");
  printInt();
  return 0;
}

void printInt(int x){
  printf("%d\n", x);
}

Old-Style Parameter

Es gibt nebst der oben aufgeführten, heute üblichen Parameterlisten-Deklaration auch noch eine alte Methode, welche allgemein als old-style bezeichnet und heutzutage kaum mehr verwendet wird. Bei der old-style-Variante werden die Symbole innerhalb der runden Klammern ohne Typ deklariert. Dadurch werden die zu übergebenden Typen nach aussen hin versteckt, die Deklaration kann somit nicht als Prototyp verwendet werden.

Die Typ-Zuordnung steht als eigenständige Deklaration nach den runden Klammern oder wird, wenn die Typzuordnung gänzlich fehlt, standardmässig als int angenommen.











30
#include <stdio.h>

int multiply(a, b)
int a;
int b;
{
  return a*b;
}

int main(){
  printf("%i\n", multiply(5, 6));
  return 0;
}

C-Compiler erlauben diese Syntax auch heute noch, da insbesondere in der Systemprogrammierung diese Syntax immer noch teilweise verwendet wird. C++ Compiler erlauben diesen Style jedoch nicht mehr.

Funktions-Definition

Die Signatur, also der Rückgabetyp, der Name und die Parameterliste werden umgangssprachlich auch als Funktions-Kopf (function header) bezeichnet, da sie zuoberst bei der Definition hingeschrieben werden. Nach dem Kopf kommt der sogenannte Body, der Körper der Funktion, sprich, die Ausprogrammierung der Funktionalität. Sobald eine Funktion einen Body besitzt, ist sie definiert.

Im Gegensatz zur Deklaration wird bei einer Definition der Funktion ein Body angehängt, indem anstelle des Semikolons ; der Body in geschweiften Klammern {} ausformuliert wird.

void printInt(int x){
  printf("%d\n", x);
}

Die Schreibweise, wo genau die geschweiften Klammern stehen, ist je nach Stil unterschiedlich. Die Stile sind nach berühmten Menschen benannt und spielen für den Compiler nicht die geringste Rolle. Der Autor hat sich an oben aufgeführte Schreibweise gewöhnt, die folgende Schreibweise ist jedoch ebenfalls häufig anzutreffen:

void printInt(int x)
{
  printf("%d\n", x);
}

Innerhalb der geschweiften Klammern wird die Funktion ausprogrammiert. Die geschweiften Klammern bilden hierbei den Function scope, also den Definitionsbereich der Funktion. Innerhalb dieses Bereiches stehen die Variablen-Definitionen und der ausführbare Code der Funktion.

Es sei angemerkt, dass der C90-Standard vorschreibt, dass innerhalb eines Funktions-Blockes sämtliche Variablen-Definitionen VOR ausführenden Operationen stehen müssen. Eine detailierte Ausführung kann bei den Anweisungs-Blöcken nachgelesen werden. Ab dem Standard C99 ist diese Restriktion aufgehoben worden.

Die Variablen der Parameterliste werden vom Compiler automatisch dem Definitionsbereich der Funktion hinzugefügt und stehen somit innerhalb der Funktion zur Verfügung. Eine Funktion wird mittels der return-Anweisung beendet. Konsequenterweise sind ab diesem Zeitpunkt die Variablen innerhalb der Funktion (wie auch die Parameter) nicht mehr gültig. Detailiertere Informationen dazu können beim Call-Stack nachgelesen werden.

Wird ein Parameter innerhalb des Bodys der Funktion nicht benutzt, so geben moderne Compiler je nach Einstellung eine Warnung aus wie unused parameter. Um solche Warnungen zu unterdrücken, kann der Parameter innerhalb des Bodys einfach nach void gecastet werden.

Terminologie für Funktionen

Für Funktionen gibt es viele verfeinerte Begriffe, die jedoch in den Sprachen C und C++ kaum verwendet werden. Der Begriff Prozedur beispielsweise bezeichnet eine Funktion OHNE Rückgabewert. In C und C++ ist die Deklaration einer Prozedur genau gleich wie diejenige einer Funktion, nur dass sie als Rückgabetyp das Keyword void verwendet.

Der Begriff Handler wird hauptsächlich verwendet für Funktionszeiger, wenn sie für sogenannte Callback-Functions eingesetzt werden. Ein Handler ist zu gut Deutsch eine Behandlung. Er wird in C++ auch für das Exception-Handling mittels try-catch verwendet. Der Begriff Routine ist zu verstehen als eine beliebige Befehlsfolge, welche jedoch einen in-sich-geschlossenen Charakter hat. Der Terminus wird ebenfalls oftmals verwendet für das Exception-Handling mittels der try-catch-Struktur Im Unterschied zum Handler bezeichnet eine Routine grundsätzlich immer eine vorliegende, ausprogrammierte Befehlsfolge. Der Begriff Handler wird mit symbolischem Charakter verwendet, da er zum einen für Funktions-Pointer verwendet werden kann, dessen genauer Inhalt nicht zwingend bekannt sein muss, zum anderen, da der Begriff als allgemeiner Überbegriff im Sinne einer beliebigen Behandlung verwendet wird: Das Handling.

Vorsicht: Nebst dem Handler gibt es auch noch das Handle, was jedoch grundsätzlich mit Funktionen wenig zu tun hat. Ein Handle bezeichnet ganz allgemein einen Pointer oder eine Referenz auf ein Objekt. Etwas spezifischer bezeichnet das Objekt (normalerweise) eine vom System zur Verfügung gestellte Struktur, welche (normalerweise) nicht im Code direkt angesprochen werden kann. Handle bedeutet zu gut Deutsch: Der Handgriff, die Halterung, der Henkel. Mit einem Handle lässt sich quasi ein Objekt begrapschen, ohne sich die Finger zu verbrennen. Es hat sich zudem noch eine weitere, häufig gesehene Definition des Handles entwickelt: Ein Pointer auf einen Pointer. Mittels eines Pointers auf einen Pointer können Objekte über mehrere Funktionsebenen hinweg übergeben, verändert, gelöscht oder neu gesetzt werden, ohne dass das Handle selbst seinen Wert ändert.

Der Begriff Sub-Routine wird in C und C++ nicht verwendet und ist hier nur der Vollständigkeit halber aufgeführt. Im eigentlichen Sinne bezeichnet eine Sub-Routine das, was in C und C++ als Funktion bezeichnet wird, allerdings hat eine Sub-Routine den Charakter einer Hilfs-Funktion. In anderen Programmiersprachen wie beispielsweise BASIC gibt es für Subroutinen eine eigene Kontrollstruktur (GOSUB), welche einen einfachen Sprung an die Adresse der Subroutine ausführt, ohne jedoch den Kontext der Variablen zu ändern. Nach Abarbeitung der Sub-Routine wird zur aufrufenden Funktion zurückgekehrt. Der Aufruf einer Sub-Routine ist sozusagen ein vereinfachter Funktionsaufruf.

In früheren Zeiten war es in C möglich, Funktionen innerhalb von Funktionen zu definieren. Diese sogenannten nested functions (eingenistete Funktionen) werden jedoch in C heutzutage nicht mehr verwendet (ausser möglicherweise in der Systemprogrammierung) und sind bei modernen Compilern standardmässig ausgeschaltet. In der Sprache C++ gibt es keine nested functions. Auf dieser Seite wird dementsprechend nicht weiter darauf eingegangen.