9. Funktionen

Bisher waren die Programme noch recht klein. Wird die zu lösende Aufgabe komplexer, so sollte das Programm in übersichtliche Teile zerlegt werden. Diese Teile können dann immer noch so komplex sein, dass sie weiter unterteilt werden usw. Der Entwurf dieser Hierarchie nennt sich Prinzip der schrittweisen Verfeinerung.

Diese Teilprogramme werden Funktionen genannt. Eine Funktion erledigt eine abgeschlossene Teilaufgabe. Weiterhin müssen der Funktion die zu verarbeitenden Werte mitgegeben werden. Dieses geschieht mit sogenannten Argumenten bzw. Parametern. Die Funktion liefert das Ergebnis an die aufrufende Funktion zurück. Dieses Ergebnis wird auch Rückgabewert genannt. Ein Beispiel ist die Sinus-Funktion y = sin(x): Hier wird der Wert x als Parameter der Funktion übergeben und das Ergebnis der Sinus-Funktion wird der Variablen y zugewiesen.

Eine Funktion muss nur einmal definiert werden. Danach kann sie beliebig oft durch Nennung ihres Namens (dem Funktionsnamen) aufgerufen werden. Eine Funktion ist also wiederverwendbar!

9.1. Funktionsprototypen und -definitionen

Bei Funktionen wird zwischen der Deklaration (Bekanntmachung des Namens) und der Definition (Belegung eines Speicherbereichs) unterschieden. Die Deklaration der Funktionen wird im allgemeinen vor dem Hauptprogramm main() geschrieben. Diese Deklarationen werden hier Funktionsprototypen genannt. Die Syntax eines Funktionsprototyps sieht folgendermaßen aus:

Rückgabe-Datentyp Funktionsname(Datentyp [Parametername], ...);

Die Parameter werden bei Funktionsprototypen und -definitionen auch Formalparameter genannt. Die Parameteranzahl kann bei jeder Funktion unterschiedlich sein. Es können Funktionen mit einer Parameteranzahl zwischen 0 und "beliebig viele" deklariert und definiert werden. Beim Aufruf der Funktion dagegen muss exakt die bei der Deklaration bzw. der Definition vorgegebene Anzahl der Parameter angegeben werden. Der Name des Parameters muss bei der Deklaration nicht angegeben werden, wohl aber bei der Definition.

Durch die Deklaration "weiß" der Compiler, dass irgendwo eine Funktion mit dem angegebenen Funktionsnamen definiert ist, wieviele Parameter sie hat und welchen Datentyp der Rückgabewert hat. Wird diese Funktion im Programm aufgerufen, kann der Compiler jetzt eine Syntaxprüfung machen, ohne dass die Funktion definiert sein muss.

Erst durch die Funktionsdefinition kann die Funktion auch angewendet werden, denn erst hier wird der Quellcode angegeben und wird entsprechend Speicherplatz dafür reserviert. Die Syntax einer Funktionsdefinition sieht folgendermaßen aus:

Rückgabe-Datentyp Funktionsname(Datentyp Parametername, ...)
{  Anweisungen;
}


Der wichtigste Unterschied in der Syntax zwischen Funktionsprototyp und -definition ist, dass beim Prototyp am Ende der Zeile ein Semikolon steht, während bei der Definition kein Semikolon stehen darf. Dafür folgt bei der Definition in den darauffolgenden Zeilen der Quellcode der Funktion, der sogenannte Funktionskörper. Der Quellcode der Funktion wird - auch wenn die Funktion nur eine Anweisung beinhaltet - zwischen einem Paar geschweifte Klammern gesetzt (genauso wie bei der main-Funktion).

Beispiel:

kap09_01.c

01 #include <stdio.h>
02
03 /* Funktionsprototyp: */
04 double Average(double, double, double);
05
06 /* Hauptprogramm: */
07 int main()
08 {
09    double a = 4.5, b = 3.1415, c = 7.99;
10
11    /* Aufruf der Funktion in der printf-Anweisung: */
12    printf("Durchschnitt: %f\n", Average(a, b, c));
13
14    return 0;
15 }
16
17 /* Funktionsdefinition: */
18 double Average(double Zahl1, double Zahl2, double Zahl3)
19 {
20    return (Zahl1 + Zahl2 + Zahl3) / 3.0;
21 }

Aufgerufen wird die Funktion durch den Funktionsnamen gefolgt von den Parametern, die in Klammern gesetzt werden. Die Parameter werden in diesem Fall Aktualparameter genannt. Die Klammern müssen auch dann gesetzt werden, wenn keine Parameter der Funktion übergeben werden. Die Syntax für einen Funktionsaufruf lautet also

Funktionsname(Variablenname bzw. Konstante, ...);

Der Rückgabewert der Funktion kann in einer Variablen gespeichert werden oder wie im Beispiel gleich einer anderen Funktion übergeben werden (printf ist auch eine Funktion) oder einfach "vergessen" werden, wenn das Funktionsergebnis nicht benötigt wird. Z.B. liefert auch die printf-Funktion ein Ergebnis zurück (nämlich die Anzahl der ausgegebenen Zeichen), aber dieses Ergebnis wird nicht weiter benötigt, also wird es auch nicht weiter verarbeitet.

9.2. Gültigkeitsbereiche und Sichtbarkeit

Grundsätzlich sind alle deklarierten Namen von Variablen, Typen, Konstanten, Funktionen, usw. nach der Deklaration nur innerhalb des Blocks gültig, in dem sie deklariert wurden. D.h. z.B. alle Variablen, die innerhalb einer Funktion deklariert bzw. definiert werden, sind nur innerhalb dieser Funktion bekannt und sichtbar; in jeder anderen Funktion sind diese Variablen unbekannt. Diese Variablen werden lokale Variablen genannt; sie sind nur in der lokalen Umgebung (innerhalb des Blocks) bekannt. Nach Verlassen des Blocks werden sie wieder vernichtet, d.h. ihr Speicherplatz wird wieder freigegeben.

Variablen, die außerhalb von allen Blöcken deklariert werden, sind innerhalb der ganzen Datei gültig, d.h. innerhalb der main- und innerhalb aller anderen Funktionen, die in der gleichen Quellcodedatei definiert sind. Dadurch, dass diese Variablen global gelten, werden sie auch globale Variablen genannt.

Grundsätzlich sollte auf globale Variablen nach Möglichkeit verzichtet werden und statt dessen lieber diese Variablen lokal angelegt und bei Bedarf als Parameter an die Funktionen übergeben werden.

Im folgenden Beispielprogramm gibt der Compiler jeweils eine Fehlermeldung für die Zeilen 13 und 24 aus, da in beiden Fällen die Variablen nicht bekannt (deklariert) sind.

Beispiel:

kap09_02.c

01 #include <stdio.h>
02
03 int a;           /* globale Variable */
04
05 void Test(void); /* keine Parameter, kein Rückgabewert */
06
07 int main()
08 {
09    int b;        /* lokal im Hauptprogramm */
10
11    a = 0;        /* erlaubt, da a global */
12    b = 0;        /* erlaubt, da b in diesem Block deklariert */
13    c = 0;        /* FEHLER!, da c nur in Funktion bekannt */
14    Test();
15
16    return 0;
17 }
18
19 void Test(void)
20 {
21    int c;        /* lokal in dieser Funktion */
22
23    a = 0;        /* erlaubt, da a global */
24    b = 0;        /* FEHLER!, da b nur im Hauptprogramm bekannt */
25    c = 0;        /* erlaubt, da c in dieser Funktion deklariert */
26 }

Nach diesen Angaben ist es nun auch möglich, mitten im Programm einen Block einzusetzen und in diesem Block lokale Variablen zu deklarieren bzw. zu definieren. Das sieht dann wie folgt aus.

Beispiel:

kap09_03.c

01 #include <stdio.h>
02
03 int main()
04 {
05    int a;    /* lokal im Hauptprogramm */
06
07    a = 0;    /* erlaubt, da a im Hauptprogramm deklariert */
08
09    /* Block innerhalb des Hauptprogramms: */
10    {
11       int b; /* lokal in diesem Block */
12
13       a = 3; /* erlaubt */
14       b = 3; /* erlaubt */
15    }
16    b = 0;    /* FEHLER!, da b nur in dem Block bekannt */
17
18    return 0;
19 }

Eine Ausnahme bilden lokale Variable, die innerhalb eines Blocks oder einer Funktion als statische Variable definiert werden. Dazu wird vor dem Datentyp zusätzlich das Schlüsselwort static verwendet. Diese statischen Variablen werden nach Verlassen des Blocks oder der Funktion nicht vernichtet und haben bei erneutem Aufruf des Blocks oder der Funktion noch den alten Wert. Ferner wird eine evtl. Variableninitialisierung nur bei der erstmaligen Definition ausgeführt; bei allen weiteren Aufrufen wird die Initialisierung ignoriert.

Beispiel:

kap09_04.c

01 #include <stdio.h>
02
03 void Test(void);
04
05 int main()
06 {
07    int i;
08
09    for (i = 0; i < 3; i++)
10       Test();
11
12    return 0;
13 }
14
15 void Test(void)
16 {
17    static int Anzahl = 0;
18    /* wird nur beim 1. Aufruf auf Null gesetzt! */
19
20    Anzahl++;
21    printf("Anzahl = %i\n", Anzahl);
22 }

Die Ausgabe des Programms ist

Ausgabe = 1
Ausgabe = 2
Ausgabe = 3


Ohne das Schlüsselwort static würde dreimal eine 1 ausgegeben werden, da die lokale Variable bei jedem Funktionsaufruf wieder neu erzeugt würde.

Die Parameter einer Funktion werden innerhalb der Funktion als lokale Variablen behandelt; von außen betrachtet stellen sie die Daten-Schnittstelle zur Funktion dar. Mehr dazu im nächsten Abschnitt.

9.3. Funktionsschnittstelle

Der Datentransfer in Funktionen hinein (Parameter) und aus Funktionen heraus (Rückgabewert) wird durch die Beschreibung der Schnittstelle festgelegt. Unter einer Schnittstelle ist eine formale Vereinbarung zwischen Aufrufer und Funktion über die Art und Weise des Datentransports zu verstehen. Auch das, was die Funktion leistet, gehört zur Schnittstelle (sollte als Kommentar beim Funktionskopf stehen). In diesem Abschnitt soll es nur um den Datentransfer gehen. Die Schnittstelle ist durch den Funktionsprototyp eindeutig beschrieben und enthält folgendes:

• den Rückgabetyp der Funktion,
• den Funktionsnamen,
• Parameter, die der Funktion bekannt gemacht werden inkl. deren Datentypen und
• die Art der Parameterübergabe.

Der Compiler prüft, ob die Definition der Schnittstelle bei einem Funktionsaufruf eingehalten wird.

Für den Datentransfer in die Funktion hinein gibt es zwei verschiedene Arten des Datentransports: Die Übergabe per Wert und die Übergabe per Zeiger.

Übergabe per Wert

Bei der Übergabe per Wert wird der Wert in den sogenannten Stack (einem Zwischenspeicher u.a. für die Parameterübergabe) kopiert - daher wird dies manchmal auch Übergabe per Kopie genannt. Innerhalb der Funktion werden die Werte auf dem Stack als lokalen Variable verwendet, die am Ende der Funktion wieder vernichtet werden, während die Originale unverändert bleiben.

Beispiel:

kap09_05.c

01 #include <stdio.h>
02
03 int Addiere_3(int);
04
05 int main()
06 {
07    int Ergebnis, Zahl = 2;
08
09    printf("Wert von Zahl = %i\n", Zahl);
10    Ergebnis = Addiere_3(Zahl);
11    printf("Ergebnis 'Addiere_3(Zahl)' = %i\n", Ergebnis);
12    printf("Wert von Zahl = %i (unveraendert)\n", Zahl);
13
14    return 0;
15 }
16
17 int Addiere_3(int x)
18 {
19    x += 3;
20    return x;
21 }

Die Übergabe per Wert sollte generell verwendet werden, wenn ein Objekt von der Funktion nicht verändert werden soll. Wenn der Kopiervorgang in den Stack allerdings zu lange dauert (bei sehr großen Objekten oder bei häufigem Aufruf der Funktion), kann auch eine Übergabe per Zeiger geschehen. Dann sollte allerdings sichergestellt werden, dass die Funktion den übergebenen Wert nicht verändert, z.B. durch Verwendung von const-Parametern.

Übergabe per Zeiger

Die Übergabe per Zeiger ist ein Spezialfall der Übergabe per Wert, es wird nämlich der Zeiger auf das Objekt im Stack abgelegt. Innerhalb der Funktion wird dieser Zeiger auf dem Stack wieder als lokale Variable behandelt. Der Zeiger wird also am Ende der Funktion wieder vernichtet. Aber über diesen Zeiger kann auf das eigentliche Objekt zugegriffen (und damit auch verändert) werden. Es wird also nicht das Objekt selber übergeben, sondern ein Zeiger auf das Objekt.

Bei den Parametern wird vor dem Parameternamen ein Stern gesetzt, da ja ein Zeiger übergeben wird; der Datentyp dagegen wird nicht geändert. Beim Aufruf selber wird vor dem Parameternamen der Adressoperator ('&') gesetzt. Dadurch wird ein Zeiger auf das eigentliche Objekt erzeugt und der Funktion übergeben. Das obige Beispiel wird nun so geändert, dass die Zahl per Zeiger an die Funktion übergeben wird.

Beispiel:

kap09_06.c

01 #include <stdio.h>
02
03 int Addiere_3(int *);
04
05 int main()
06 {
07    int Ergebnis, Zahl = 2;
08
09    printf("Wert von Zahl = %i\n", Zahl);
10    Ergebnis = Addiere_3(&Zahl);
11    printf("Ergebnis 'Addiere_3(&Zahl)' = %i\n", Ergebnis);
12    printf("Wert von Zahl = %i (veraendert!!!)\n", Zahl);
13
14    return 0;
15 }
16
17 int Addiere_3(int *x)
18 {
19    *x += 3;
20    return *x;
21 }

Rückgabewerte

Der Rückgabewert wird innerhalb der Funktion mit dem Befehl return Wert; angegeben, wobei Wert der Rückgabewert ist. Gleichzeitig beendet dieser Befehl die Funktion, unabhängig davon, ob weitere Anweisungen folgen oder nicht. Als Beispiel wird noch einmal das obige Beispiel verwendet.

Beispiel:

kap09_07.c

01 #include <stdio.h>
02
03 int Addiere_3(int);
04
05 int main()
06 {
07    int Ergebnis, Zahl = 2;
08
09    printf("Wert von Zahl = %i\n", Zahl);
10    Ergebnis = Addiere_3(Zahl);
11    printf("Ergebnis 'Addiere_3(Zahl)' = %i\n", Ergebnis);
12    printf("Wert von Zahl = %i (unveraendert)\n", Zahl);
13
14    return 0;
15 }
16
17 int Addiere_3(int x)
18 {
19    x += 3;
20    return x;
21    printf("STOP!"); /* Diese Anweisung wird nie ausgefuehrt! */
22 }

Bei der Rückgabe von Zeigern muss darauf geachtet werden, dass das dazugehörige Objekt auch in der aufrufenden Funktion existiert. Wenn z.B. ein Zeiger auf eine lokale Variable zurückgegeben wird, greift die aufrufende Funktion auf einen Speicherbereich zu, der eventuell bereits von einem anderen Programm oder einer anderen Funktion verwendet wird. Das Ergebnis ist dann u.U. falsch. Im schlimmsten Fall kann sogar ein Systemabsturz erzeugt werden! Das folgende Beispiel zeigt, wie es falsch ist, obwohl der Compiler u.U. keine Fehler und keine Warnungen anzeigt und das Programm sogar läuft.

Beispiel:

kap09_08.c

01 #include <stdio.h>
02
03 int *Maximum(int, int);
04
05 int main()
06 {
07    int x = 17, y = 4, *zp1, *zp2, z;
08
09    zp1 = Maximum(x, y);
10    zp2 = Maximum(y, x);
11    z = *zp1;          /* Ergebnis 1. Fkt.aufruf zwischenspeichern */
12    printf("x = %i\n", x);
13    printf("y = %i\n", y);
14    printf("z = %i\n", z);               /* Ergebnis 1. Fkt.aufruf */
15    printf("Maximum(%i, %i) = %i\n", x, y, *zp1); /* 1. Fkt.aufruf */
16    printf("Maximum(%i, %i) = %i\n", y, x, *zp2); /* 2. Fkt.aufruf */
17
18    return 0;
19 }
20
21 int *Maximum(int a, int b)
22 {
23    /* a und b sind lokale Kopien!!! */
24    return a > b ? &a : &b; /* Fehler!!! */
25 }

Zuerst wird der Zeiger zp1 auf die lokale Variable a gesetzt (da x > y ist). Anschließend wird die gleiche Funktion noch einmal aufgerufen. Dabei werden die lokalen Variablen a und b, die mit hoher Wahrscheinlichkeit die gleichen Speicheradressen belegen, auf andere Werte gesetzt. Damit zeigt auch zp1 auf einen anderen Wert. Damit ist das Ergebnis in z und in *zp1 falsch!

9.4. Rekursiver Funktionsaufruf

Der Aufruf einer Funktion durch sich selbst wird Rekursion genannt. Eine Rekursion muss irgendwann auf eine Abbruchbedingung stoßen, damit die Rekursion nicht unendlich ist. Bei einer unendlichen Rekursion wird irgendwann ein sogenannter Stacküberlauf (stack overflow) erzeugt; das Programm wird damit abgebrochen.

Als Beispiel für die Verwendung einer Rekursion wird die Quersumme einer ganzen Zahl berechnet: Eine Funktion ermittelt die letzte Ziffer einer Zahl, addiert diese zur Quersumme und ruft sich selbst mit den restlichen Ziffern wieder auf. Das Prinzip dieser Funktion lässt sich in zwei Sätze zusammenfassen:

1. Die Quersumme der Zahl 0 ist gleich 0. Dies ist die Abbruchbedingung für die Rekursion!

2. Die Quersumme einer Zahl ist gleich der letzten Ziffer plus der Quersumme der Zahl, die um diese Ziffer gekürzt wurde.

Die Quersumme von 873956 ist also gleich 6 plus der Quersumme von 87395 und ist damit gleich 6 plus 5 plus der Quersumme von 8739 usw. Auf jede Quersumme wird der Satz 2 angewandt, bis der Satz 1 gilt. Satz 1 gilt dann, wenn alle Ziffern von der Zahl abgetrennt wurden. Die letzte Ziffer der Zahl wird durch modulo 10 (Divisionsrest) abgetrennt. Die Zahl ohne der letzten Ziffer wird durch eine ganzzahlige Division durch 10 erhalten.

Beispiel:

kap09_09.c

01 #include <stdio.h>
02
03 int Quersumme(unsigned long);
04
05 int main()
06 {
07    unsigned long Zahl;
08
09    printf("Bitte eine positive ganze Zahl eingeben: ");
10    scanf("%lu", &Zahl);
11    printf("Quersumme von %u ist %i\n", Zahl, Quersumme(Zahl));
12
13    return 0;
14 }
15
16 int Quersumme(unsigned long x)
17 {
18    int letzteZiffer = 0;
19
20    if (!x) /* if (x == 0) Abbruchbedingung */
21       return 0;   /* Abbruch der Rekursion */
22    else
23    {
24       letzteZiffer = x % 10;  /* modulo 10 */
25       return letzteZiffer + Quersumme(x / 10); /* Rekursion */
26    }
27 }

Die Funktion Quersumme kann auch nicht-rekursiv geschrieben werden, in dem eine Schleife verwendet wird. Diese Variante wird iterativ genannt.

kap09_10.c

01 #include <stdio.h>
02
03 int Quersumme(unsigned long);
04
05 int main()
06 {
07    unsigned long Zahl;
08
09    printf("Bitte eine positive ganze Zahl eingeben: ");
10    scanf("%lu", &Zahl);
11    printf("Quersumme von %u ist %i\n", Zahl, Quersumme(Zahl));
12
13    return 0;
14 }
15
16 int Quersumme(unsigned long x)
17 {
18    int QSumme = 0;
19
20    while (x > 0)
21    {
22       QSumme += x % 10; /* letzte Ziffer addieren  */
23       x /= 10;          /* letzte Ziffer abtrennen */
24    }
25    return QSumme;       /* Rueckgabe der Quersumme */
26 }

9.5. Zeiger auf Funktionen

Steht zum Zeitpunkt der Compilierung noch nicht fest, welche Funktion zur Laufzeit aufgerufen werden soll (z.B. wenn der Anwender zur Laufzeit erst die gewünschte Funktion auswählt) oder an welcher Adresse die Funktion steht (z.B. wenn Funktionen zur Laufzeit nachgeladen werden und daher deren Adressen beim Kompilieren noch nicht bekannt sind), wird mit Zeigern auf Funktionen gearbeitet.

Das folgende Beispiel soll den Umgang mit Zeigern auf Funktionen verdeutlichen:

Beispiel:

kap09_11.c

01 #include <stdio.h>
02 #include <math.h>
03
04 void Funktionswerte(double (*)(double));
05
06 int main()
07 {
08    double (*TrigFkt)(double); /* Zeiger auf Funktion, die ein
09                                  double als Parameter erhaelt
10                                  und ein double zurueckgibt.      */
11
12    TrigFkt = sin;             /* Zeiger auf sin-Funktion zuweisen */
13    Funktionswerte(TrigFkt);
14
15    return 0;
16 }
17
18 void Funktionswerte(double (*Fkt)(double))
19 {
20    static const double PI = 4 * atan(1);
21    double x;
22
23    for (x = 0; x < PI; x += 0.01)
24       printf("f(%5.2f) = %5.2f\n", x, Fkt(x));
25 }

Auch ein Array von Zeigern auf Funktionen ist möglich. Dazu wird gleich hinter dem Zeigernamen der Index angegeben.

Beispiel:

kap09_12.c

01 #include <stdio.h>
02 #include <math.h>
03
04 void Funktionswerte(double (*)(double));
05
06 int main()
07 {
08    double (*TrigFkt[2])(double); /* Array von Zeigern auf Funktionen,
09                                     die ein double als Parameter
10                                     erhalten und ein double
11                                     zurueckgeben.                  */
12
13    TrigFkt[0] = sin;             /* Zeiger auf sin-Fkt. zuweisen   */
14    TrigFkt[1] = cos;             /* Zeiger auf cos-Fkt. zuweisen   */
15    Funktionswerte(TrigFkt[0]);
16    Funktionswerte(TrigFkt[1]);
17
18    return 0;
19 }
20
21 void Funktionswerte(double (*Fkt)(double))
22 {
23    static const double PI = 4 * atan(1);
24    double x;
25
26    for (x = 0; x < PI; x += 0.01)
27       printf("f(%5.2f) = %5.2f\n", x, Fkt(x));
28 }

Zeiger auf Funktionen scheinen aber nur unzureichend standardisiert zu sein, da einige Compiler diese Beispiele nicht korrekt verarbeiten können.

9.6. Die Funktion main()

Die Funktion main() - das Hauptprogramm - ist eine spezielle Funktion. Jedes C-/C++-Programm startet definitionsgemäß mit main(), so dass main() in jedem Programm genau einmal vorhanden sein muss. Der Rückgabewert ist standardmäßig int, aber viele Compiler lassen auch void zu (dann ist natürlich auch kein return 0; mehr erlaubt!). Die main()-Funktion kann nicht überladen werden. Die zwei folgenden Formen werden von allen Compilern unterstützt.

einfache Form:

int main()
{  ...
   return 0; /* Exit-Code */
}


allgemeine (komplexere) Form:

int main(int argc, char* argv[])
{  ...
   return 0; /* Exit-Code */
}


In der zweiten Form werden zwei Parameter übergeben. Über diese beiden Parameter kann auf alle Kommandozeilenparameter zugegriffen werden, die beim Aufruf des Programms angegeben wurden. Das folgende Beispiel startet das Programm prog mit 4 Kommandozeilenparameter.

Beispiel:

./prog 1 Test 547.32 3

Der erste Parameter argc ist die Anzahl der Kommandozeilenparameter einschließlich des Programmaufrufs, im Beispiel gleich 5. Der zweite Parameter ist ein String-Array, also ein Array von Zeichenketten. In diesen Zeichenketten stehen die einzelnen Kommandozeilenparameter. Für das obige Beispiel sind folgende Werte in diesem Array gespeichert:

argv[0] = "./prog"
argv[1] = "1"
argv[2] = "Test"
argv[3] = "547.32"
argv[4] = "3"
argv[5] = NULL


Wichtig: Auch die Zahlen-Kommandozeilenparameter werden hier als Texte gespeichert.

Nun folgt ein Beispielprogramm, dass die Anzahl sowie alle Kommandozeilenparameter auflistet.

Beispiel:

kap09_13.c

01 #include <stdio.h>
02
03 int main(int argc, char *argv[])
04 {
05    int i = 1;
06
07    printf("Programmaufruf: %s\n", argv[0]);
08    printf("Anzahl Kommandozeilenparameter: %i\n", argc - 1);
09    printf("Kommandozeilenparameter:\n");
10    while (argv[i])
11    {
12       printf("%2i: %s\n", i, argv[i]);
13       i++;
14    }
15
16    return 0;
17 }


Voriges Kapitel: 8. Zeiger