Funktionsdefinitionen in C
Das Programm "kuckuck.c" gibt die ersten drei Strophen eines Liedes aus.
Kuckuck.c
#include <stdio.h>
int main( void )
{
{ puts( "Auf einem Baum ein Kuckuck, -" );
{ puts( "Sim sa la dim, bam ba," );
puts( "Sa la du, sa la dim -" ); }
puts( "Auf einem Baum ein Kuckuck sass." ); }
{ puts( "Da kam ein junger Jaeger, -" );
{ puts( "Sim sa la dim, bam ba," );
puts( "Sa la du, sa la dim -" ); }
puts( "Da kam ein junger Jaegersmann." ); }
{ puts( "Der schoss den armen Kuckuck, -" );
{ puts( "Sim sa la dim, bam ba," );
puts( "Sa la du, sa la dim -" ); }
puts( "Der schoss den armen Kuckuck tot." ); }}stdout
Auf einem Baum ein Kuckuck, -
Sim sa la dim, bam ba,
Sa la du, sa la dim -
Auf einem Baum ein Kuckuck sass.
Da kam ein junger Jaeger, -
Sim sa la dim, bam ba,
Sa la du, sa la dim -
Da kam ein junger Jaegersmann.
Der schoss den armen Kuckuck, -
Sim sa la dim, bam ba,
Sa la du, sa la dim -
Der schoss den armen Kuckuck tot.
In C kann ein Name an eine Verbundanweisung gebunden werden. Dadurch wird eine Funktion definiert.
Im Programm "kuckuck3.c" wird eine Funktion zur Ausgabe des Kehrreimes definiert und dreimal aufgerufen. Die Ausgabe des Programms "kuckuck3.c" ist der Ausgabe des Programms "kuckuck.c" gleich, denn bei jedem Aufruf der Funktion "kehrreim" wird wieder die Verbundanweisung ausgeführt, die den Kehrreim ausgibt.
kuckuck3.c
#include <stdio.h>
void kehrreim( void )
{ puts( "Sim sa la dim, bam ba," );
puts( "Sa la du, sa la dim -" ); }
int main( void )
{ { puts( "Auf einem Baum ein Kuckuck, -" ); kehrreim();
puts( "Auf einem Baum ein Kuckuck sass." ); }
{ puts( "Da kam ein junger Jaeger, -" ); kehrreim();
puts( "Da kam ein junger Jaegersmann." ); }
{ puts( "Der schoss den armen Kuckuck, -" ); kehrreim();
puts( "Der schoss den armen Kuckuck tot." ); }}stdout
Auf einem Baum ein Kuckuck, -
Sim sa la dim, bam ba,
Sa la du, sa la dim -
Auf einem Baum ein Kuckuck sass.
Da kam ein junger Jaeger, -
Sim sa la dim, bam ba,
Sa la du, sa la dim -
Da kam ein junger Jaegersmann.
Der schoss den armen Kuckuck, -
Sim sa la dim, bam ba,
Sa la du, sa la dim -
Der schoss den armen Kuckuck tot.
Der Quelltext enthält zwei Funktionsdefinitionen, die mit dem Quelltext "void kehrreim( void )" bzw. dem Quelltext "int main( void )" beginnen und die Funktion "kehrreim" sowie die Funktion "main" definieren.
Die erste Funktionsdefinition definiert die Funktion "kehrreim", indem der Name "kehrreim" an die Verbundanweisung "{ puts( "Sim sa la dim, bam ba," ); puts( "Sa la du, sa la dim -" ); }" gebunden wird. Dadurch kann dieser Name dann in der Funktion "main", auch wiederholt, aufgerufen werden, um die Ausführung dieser Verbundanweisung zu bewirken.
Ein Wort, welches in einem gerundeten Kästchen eines Syntaxdiagramms vorkommt, wird Schlüsselwort genannt. (Eine Ausnahme ist »printf«. Dieses Wort wurde früher in einem gerundeten Kästchen eines Syntaxdiagrammes gezeigt, es ist aber kein Schlüsselwort.) Ein Schlüsselwort darf nicht als Name (beispielsweise als Name einer Funktion) verwendet werden.
Das Schlüsselwort "void" legt fest, daß die definierte Funktion keine Wertfunktion ist: Der Aufruf der Funktion ergibt also keinen Wert. Es handelt sich dann um eine reine Wirkfunktion, deren Sinn eine bestimmte Wirkung ist. Im Falle der Funktion "kehrreim" ist diese Wirkung die Ausgabe des Kehrreimes.
Das Schlüsselwort "void" in den runden Klammern legt fest, daß die Funktion ohne Argumente (also mit leeren Klammern) aufgerufen werden muß.
Die „Ausführung“ eines Programms beginnt in C immer bei der Funktion "main", die in der hier angegebenen Form (oder einer Variante, die in einer anderen Lektion behandelt wird) definiert werden muß. Die Funktion "main" (engl.: main, Haupt-) ist damit sozusagen die „Hauptfunktion“ eines Programms, oder der vorgegebene „Einstiegspunkt“.
Syntax
Eine Funktionsdefinition besteht, etwas vereinfacht, aus optionalen Deklarationsspezifizierern 〈declaration-specifiers 〉, einem Deklarator 〈declarator 〉 und einer Verbundanweisung 〈compound-statement 〉.
- 〈function-definition 〉 ::=
- 〈declaration-specifiers 〉 〈declarator 〉 〈compound-statement 〉.
Ohne zu sehr in die Details zu gehen, kann man sagen, daß das am Anfang der Funktionsdefinitionen verwendete Schlüsselwort "void", bzw. das Schlüsselwort "int" ein Deklarationsspezifizierer ist.
Auf den Deklarationsspezifizierer folgt dann der Deklarator, der in den hier gegebenen Beispielen durch den Deklarator "kehrreim( void )" bzw. den Deklarator "main( void )" gegeben ist. Die darauf folgende Verbundanweisung beginnt und endet mit einer geschweiften Klammer.
Man kann sich den Aufbau einer Funktionsdefinition auch zerlegt denken in einen Kopf und einen Rumpf (oder eine Funktionsverbundanweisung ). Der Kopf legt dabei vorwiegend die Schnittstelle der Funktion fest, also ihre nach außen direkt sichtbaren Eigenschaften, und der Rumpf enthält dann die Implementation der Funktion, also das, was die Funktion bei einem Aufruf macht.
kehrreimAusgeben [Funktionsdefinition]
void kehrreim( void ) /* Kopf, Schnittstelle */
{ puts( /* Rumpf, Funktions-Verbundanweisung, Implementation */
"Sim sa la dim, bam ba," ); /* */
puts( /* */
"Sa la du, sa la dim -" ); /* */
} /* */stdout
Sim sa la dim, bam ba,
Sa la du, sa la dim -
In einer Übersetzungseinheit (einem Quelltext) können verschiedene Funktionsdefinition hintereinander stehen.
Funktionsdefinition dürfen aber nicht ineinander verschachtelt sein, es ist also nicht erlaubt, innerhalb einer Funktionsdefinition eine weitere Funktionsdefinition zu schreiben.
Nachdem eine Funktion definiert wurde, kann sie in folgenden Funktionen aufgerufen werden.
In dem Text "Kuckuck4.txt" wird eine Funktion "kehrreim" aufgerufen, die aber an der Stelle des Aufrufs noch nicht bekannt ist. Erst weiter unten wird eine Funktion "kehrreim" definiert. Daher ist dieser Text keine gültige Übersetzungseinheit.
Kuckuck4.txt
#include <stdio.h>
int main( void )
{ { puts( "Auf einem Baum ein Kuckuck, -" ); kehrreim();
puts( "Auf einem Baum ein Kuckuck sass." ); }
{ puts( "Da kam ein junger Jaeger, -" ); kehrreim();
puts( "Da kam ein junger Jaegersmann." ); }
{ puts( "Der schoss den armen Kuckuck, -" ); kehrreim();
puts( "Der schoss den armen Kuckuck tot." ); }}void kehrreim( void )
{ puts( "Sim sa la dim, bam ba," );
puts( "Sa la du, sa la dim -" ); }Konsole
"Kuckuck4.txt", line 3: error: identifier "kehrreim" is undefined
{ { puts( "Auf einem Baum ein Kuckuck, -" ); kehrreim();
^"Kuckuck4.txt", line 5: error: identifier "kehrreim" is undefined
{ puts( "Da kam ein junger Jaeger, -" ); kehrreim();
^"Kuckuck4.txt", line 7: error: identifier "kehrreim" is undefined
{ puts( "Der schoss den armen Kuckuck, -" ); kehrreim();
^"Kuckuck4.txt", line 9: error: declaration is incompatible with previous "kehrreim"
(declared at line 3)
void kehrreim()
^4 errors detected in the compilation of "Kuckuck4.txt".
Zum besseren Verständnis des Ablaufes eines Programmes mit mehreren Funktionen, kann es helfen, vorübergehend am Anfang und am Ende einer Funktionsverbundanweisung eine Ausgabeanweisung einzufügen, die anzeigt, daß die Funktion gestartet bzw. verlassen wird. Dieses ist besonders zu Fehlersuche hilfreich, wenn das Programm nicht so läuft, wie erwartet.
kehrreim [Funktionsdefinition]
void kehrreim( void )
{ puts( "(Kehrreim, Anfang)" );
puts( "Sim sa la dim, bam ba," );
puts( "Sa la du, sa la dim -" );
puts( "(Kehrreim, Ende)" ); }stdout
(Kehrreim, Anfang)
Sim sa la dim, bam ba,
Sa la du, sa la dim -
(Kehrreim, Ende)
In C ist es üblich, Funktionsnamen ganz aus kleinen Buchstaben zu formen. Es gibt aber auch Programmierer, die von dieser Regel abweichen.
Schreibweisen kurzer Funktionen
Der Leerraum vor der ersten geschweiften Klammer ist nicht nötig. Daher sind die beiden folgenden Programme gleichwertig.
main.c
#include <stdio.h>
int main( void )
{ puts( "Sim sa la dim" ); }
main.c
#include <stdio.h>
int main( void ){ puts( "Sim sa la dim" ); }
Zeilenendkommentare
Durch das Zeichenpaar »//« wird Text bis zum Ende der Zeile als Kommentar gekennzeichnet. (Zwischen die beiden Schrägstriche darf dabei kein Leerraum eingefügt werden.)
main.c
#include <stdio.h>
int main( void )
{ printf
( "%d\n", -65 ); } // Vorzeichenwechselstdout
-65
Innerhalb eines Zeilenendkommentars haben die Zeichenpaare »/*«, »*/« und »//« keine besondere Bedeutung.
Einen mit »/*« eingeleiteten Kommentar nennen wir zur Unterscheidung von einem Zeilenendkommentar auch einen traditionellen Kommentar.
„Auskommentieren“ von Quelltext
Auch Quelltext kann in einem Kommentar enthalten sein. Der Quelltext wird dann vom Compiler ignoriert. Man nennt ihn auskommentiert.
main.c
#include <stdio.h>
int main( void )
{ printf
( "%d\n", 65 ); }/*
// fruehere Programmversion vom 18. Januar 2036 (hier auskommentiert):
#include <stdio.h>
int main( void )
{ printf
( "%d\n", 64 ); }*/
stdout
65
- Was der Compiler davon sieht:
#include <stdio.h>
int main( void )
{ printf
( "%d\n", 65 ); }
Jedoch ist es nicht möglich, mit einem traditionellen Kommentar Programme auszukommentieren, die selber traditionelle Kommentare enthalten, da traditionelle Kommentare nicht verschachtelt werden dürfen (weil das erste »*/« den Kommentar immer beendet). Hier muß dann auf Zeilenendkommentare ausgewichen werden:
main.c
#include <stdio.h>
int main( void )
{ printf
( "%d\n", 65 ); }// /* fruehere Programmversion vom 4. Dezember 2036 */
// #include <stdio.h>
// int main( void )
// { printf
// ( "%d\n", 64 ); }stdout
65
- Was der Compiler davon sieht:
#include <stdio.h>
int main( void )
{ printf
( "%d\n", 65 ); }
Übungen
- ? Was ist die Ausgabe des folgenden Programms?
#include <stdio.h>
void alpha( void )
{ puts( "alpha" ); }int main( void )
{ puts( "gamma" ); }- Übungsfrage Was gibt das obige Programm aus?
- Übungsfrage Ist die Funktion »alpha()« eine Wertfunktion?
- Übungsfrage Ist die Funktion »alpha()« eine Wirkfunktion?
- ? Was ist die Ausgabe des folgenden Programms?
#include <stdio.h>
void gamma( void )
{ puts( "Hallo!" ); }void alpha( void )
{ gamma(); gamma(); }int main( void )
{ alpha(); gamma(); }- Übungsfrage Was gibt das obige Programm aus?
- ? Was ist die Ausgabe des folgenden Programms?
#include <stdio.h>
int main( void )
{ puts( "." ); main(); }- Übungsfrage Was gibt das obige Programm aus?
- / Zeilenende ausgeben
- Schreiben Sie mit Hilfe des Ausdrucks »puts( "alpha" )« (dessen Auswertung das Wort »alpha« und ein Zeilenende ausgibt) eine Definition einer Funktion namens »printalpha«, die das Wort »alpha« und ein Zeilenende ausgibt.
- ? Ist der folgende Text ein syntaktisch korrektes Programm?
#include <stdio.h> int main( void )
{ puts( "." ); // An dieser Stelle schreibt das Programm einen einzelnen Punkt
auf den Bildschirm
}- Übungsfrage Was gibt das obige Programm aus? *
- ? Was ist die Ausgabe des folgenden Programms? *
#include <stdio.h>
void arg( void )
{ printf( "0" ); }int main( void )
{ printf( "floor( " ); arg(); puts( ")" ); }
/ Textausgabe
Fügen Sie eine Definition einer Funktion mit dem Namen »alpha« in den folgenden Quelltext ein, so daß durch die Auswertung des im Hauptprogramm befindlichen Aufrufausdrucks »alpha()« die Anweisung »printf( "gamma" );« ausgeführt wird.
Main.txt
#include <stdio.h>
/* Die Definition der Funktion alpha() hier einfügen */
int main( void ){ alpha(); puts( "" ); }
Bei der Bearbeitung dieser Aufgabe sollen die erste und die letzte Zeile nicht verändert werden!
Das Ergebnis der Bearbeitung sollte dann »gamma↵« ausgeben.
/ Programmieren einer NOP-Funktion »nop()« ⃗
Eine NOP-Funktion (NOP = “no operation ”) ist eine Funktion, die nichts macht. Der Aufruf einer NOP-Funktion hat also weder Wert noch Wirkung.
Schreiben Sie eine Definition einer NOP-Funktion mit dem Namen »nop«!
Als Lösung dieser Übungsaufgabe muß nicht unbedingt ein vollständiges C -Programm geschrieben werden; es reicht auch, nur eine Funktionsdefinition zu schreiben.
/ Zufallszahl ausgeben ⃗
Schreiben Sie eine Definition einer Funktion »zufallszahlAusgeben()«, so daß die Auswertung eines im Hauptprogramm befindlichen Aufrufausdrucks »zufallszahlAusgeben()« dann eine Zufallszahl ausgibt. (Bei mehrfachen Aufruf der Funktion innerhalb einer Ausführung dieses Programms sollten dann in der Regel verschiedene Zufallszahlen ausgegeben werden.)
- Funktionen definieren
- Schreiben Sie das folgende Programm um, so daß sich wiederholende Anweisungen oder Anweisungsgruppen von einer Funktion ausgeführt werden, die dann wiederholt aufgerufen wird (bzw. aufgerufen werden). Das Umschreiben soll die Ausgabe des Programmes nicht verändern.
- Für nur einmal vorkommende Anweisungen soll aber keine Funktion definiert werden.
Gemuese.c
#include <stdio.h>
int main( void )
{ puts( "Tomaten" );
puts( "Rotkohl Gruenkohl" );
puts( "Gurken" );
puts( "Tomaten" );
puts( "Rotkohl Gruenkohl" );
puts( "Spinat" );
puts( "Tomaten" );
puts( "Rotkohl Gruenkohl" );
puts( "Kohlrabi" ); }stdout
Tomaten
Rotkohl Gruenkohl
Gurken
Tomaten
Rotkohl Gruenkohl
Spinat
Tomaten
Rotkohl Gruenkohl
Kohlrabi- Hinweis Falls die Aufgabe schwerfällt, kann das Programm "Kuckuck3.c" als Vorlage für die Lösung verwendet werden, indem darin schrittweise die Texte durch die für die Lösung dieser Aufgabe passende Texte ersetzt werden.
- Ablaufanzeige (debugger ) verwenden
- Machen Sie sich damit vertraut, wie eine eventuell in Ihnen zugänglichen Entwicklungssystemen vorhandene Ablaufanzeige (ein debugger ) bedient wird, um den Ablauf von Programmen und Funktionsaufrufen beobachten zu können. (In einer Lehrveranstaltung sollte der Dozent Sie dabei unterstützen.)
Leerraumdiagramme für Leerraum *
Die folgenden Leerraumdiagramme beschreiben Leerraum einschließlich von Kommentaren. Ein genaues Verständnis dieser Diagramme ist aber für den Rest dieses Kurses nicht unbedingt wichtig.
Ein Leerraumdiagramm ist genauso zu verstehen, wie ein morphologisches Diagramm. Wir nennen es aber nicht „morphologisches Diagramm“, da morphologische Diagramme lexikalische Einheiten beschreiben und Leerraum keine lexikalische Einheit ist. Leerraumdiagramme werden daher auch nicht mit »|-|«, sondern mit »| |« gekennzeichnet.
Leerraumkomponente
.-------------.
| |----.----------------------------------------->| Leerzeichen |----.--->| |
| '-------------' ^
| |
| .-------------. |
'-------------------------------------.--->| Zeilenende |--->'
| ^ '-------------' |
| | |
v | |
.-. | |
( / ) | |
'-' | |
| | |
| .-. | |
'--->( / )---.------------------------' |
| '-' ^ | |
| | .---------------. | |
| | | jedes Zeichen | | |
| '---| ausser einem |<---' |
| | Zeilenende | |
| '---------------' |
| |
| .---------------. |
| | jedes Zeichen | |
| .---| ausser "*" |<--------. |
| | | und "/" | | |
| | '---------------' | |
| .-. v | |
'--->( * )---'---------------------------->' |
'-' | ^ ^ |
| | | |
| | | |
.<-----------. | |
| | | |
| .-. | | |
'--->( / )---' .------------. |
| '-' | | |
| | | |
| v .-. | .-. |
'----------------'--->( * )---'---( / )----------'
'-' '-'Leerraum
.--------------------.
| |---.--->| Leerraumkomponente |---.--->| |
^ '--------------------' |
| |
'-----------------------------'