6 Funktionen


6.1 Aufbau einer Funktion
6.2 Datentyp und Deklaration einer Funktion
6.3 Parameterübergabe
6.4 Speicherklassen und Geltungsbereiche von Namen
6.5 Rekursive Funktionen


- Funktionen sind Hilfsmittel, um Problemstellungen in kleine Teilprobleme zerlegen zu können

- sie dienen damit einer strukturierten Programmierung

- typische Anwendung für Funktionen ist weiterhin die Erledigung immer wiederkehrender Aufgaben

- für die wichtigsten Aufgaben gibt es bereits Funktionen, sie sind in der C-Programmbibliothek enthalten

- für eigene Zwecke kann man natürlich auch eigene Funktionen schreiben

- C Programme bestehen typischerweise aus vielen kleinen und nicht aus wenigen großen Funktionen


6.1 Aufbau einer Funktion

- eine Funktion hat einen festgelegten Aufbau, der wie folgt aussieht:


  Typ name(Parameterliste)
  {
    Vereinbarungen
    
    Anweisungen
    
    return Funktionswert, optional
  }

- die runden Klammern müssen stehen, damit name zur Funktion wird (Zwischenraum zwischen name und ( ist erlaubt

- die Parameterliste in den runden Klammern ist optional, d.h. sie muß nur vorhanden sein, wenn der Funktion wirklich Parameter übergeben werden. Andernfalls ist als Platzhalter void anzugeben

- die Parameterliste enthält sowohl die Namen als auch die Typdeklarationen der Parameter

- die Gesamtheit der Vereinbarungen und Anweisungen der Funktion selbst nennt man den Funktionskörper, er muß durch geschweifte Klammern eingeschlossen sein

- die return-Anweisung darf an beliebiger Stelle im Funktionskörper stehen, mit ihr erfolgt der Rücksprung in die aufrufende Funktion

- die return-Anweisung kann auch ganz fehlen, dann erfolgt der Rücksprung in die aufrufende Funktion beim Erreichen des Funktionsendes (der schließenden geschweiften Klammer um den Funktionkörper)

- der Funktionswert hinter der return-Anweisung wird meist in runde Klammern eingeschlossen. Dies ist aber nicht notwendig. Fehlt der Funktionswert, so wird an die aufrufende Funktion auch kein Wert zurückgegeben (siehe void-Datentyp für Funktionen)

- ebenfalls erlaubt und bei älteren C-Programmen (nicht ANSI-C) häufig zu finden ist folgender Funktionsaufbau:


  Typ name(Parameternamen, optional)
  Parameterdeklarationen, optional
  {
    Vereinbarungen
    
    Anweisungen
    
    return Funktionswert,optional
  }

- die Parameternamen müssen alle in zugehörigen Parameterdeklarationen auftauchen

- die einfachste Funktion ist die leere Funktion:


  void dummy(void) { }
  

Beispielprogramm p6-1.c:


  /*
   *      p6-1.c
   *      Beispielprogramm 1, Abschnitt 6
   *      Alle Zeilen mit Suchmuster finden
   */

  #include <stdio.h>

  #define MAXLINE 1000

  int getline(char [], int);
  int index(char [], char []);

  void main(void)
  {
    char line[MAXLINE];
  
    while (getline(line, MAXLINE - 1) > 0)
      if (index(line, "the") > 0)
	printf("%s", line);

  } /* main() */

  /* Zeile in s ablegen, Länge liefern */
  int getline(char s[], int lim)
  { 
    int c, i;
  
    i = 0;
    while (--lim > 0 && (c = getchar()) != EOF && c != '\n')
      s[i++] = c;
    if (c == '\n')
      s[i++] = c;
    s[i] = '\0';
    return(i);

  } /* getline() */

  /* Position von t in s liefern, -1 falls nicht da */
  int index (char s[], char t[])    
  { 
    int i, j, k;
  
    for (i = 0; s[i] != '\0'; i++) {
      for (j = i, k = 0; t[k] != '\0' && s[j] == t[k]; j++, k++)
	;
      if (t[k] == '\0')
	return(i);
    }
    return(-1);
  
  } /* index() */


6.2 Datentyp und Deklaration einer Funktion

- bisher haben wir nur int Funktionen besprochen

- Funktionsaufrufe und Funktionen ohne Typangabe werden wie int Funktionen behandelt

- da char immer in int gewandelt wird, sind damit auch schon die char Funktionen abgedeckt

- die meisten Funktionen sind int Funktionen

- soll eine Funktion einen anderen Datentyp als int liefern, so müssen 2 Dinge getan werden:

  1. die Funktion muß vor ihrem ersten Aufruf als eine solche mit dem gewünschten Typ deklariert werden
  2. der Funktionsdefinition selbst muß der Typ vorangestellt werden

Beispielprogramm p6-2.c


  /*
   *      p6-2.c
   *      Beispielprogramm 2, Abschnitt 6
   *      Umfang und Flaeche eines Kreises
   */

  #include <stdio.h>

  #define PI 3.1415

  void main(void)
  {
    int i;
    float radius;
    float umfang(float);
    double flaeche(float);
  
    for (i = 1, radius = 1.; i <= 10; i++, radius += .5)
      printf("Kreis %2d Radius=%6.2f Umfang=%8.4f Flaeche=\
	  %8.4f\n", i, radius, umfang(radius), flaeche(radius));

  } /* main() */

  float umfang(float rad)
  { 
    return(2. * PI * rad);

  } /* umfang() */

  double flaeche(float r)
  { 
    return(PI * r * r);

  } /* flaeche() */

Beispielprogramm p6-3.c


  /*
   *      p6-3.c
   *      Beispielprogramm 3, Abschnitt 6
   *      Zeichenkette nach double wandeln
   */

  double atof(char s[])
  { 
    double val, power;
    int i, sign;
  
    /* Zwischenraeume uebergehen */
    for (i = 0; s[i] == ' ' || s[i] == '\n' || s[i] == '\t`; i++)
      ;
    sign = 1;
    if (s[i] == '+' || s[i] == '-')       /* Vorzeichen */
      sign = (s[i++] == '+') ? 1 : -1;
    for (val = 0; s[i] >= '0' && s[i] <= '9'; i++)
      val = 10 * val + s[i] - '0';
    if s[i] == '.')
      i++;
    for (power = 1, s[i] >= '0' && s[i] <= '9'; i++) {
      val = 10 * val + s[i] - '0';
      power *= 10;
    }
    return (sign * val / power);

  } /* atof() */

- Funktionen haben oft Wirkungen, die über den Funktionswert hinausgehen (z.B. getline: Füllen eines Zeichenvektors)

- der Funktionswert dient in diesen Fällen dann häufig nur als Statusanzeige

- der in der Funktion ermittelte Funktionswert muß in der aufrufenden Funktion nicht ausgewertet, also auch nicht zugewiesen werden

- soll eine Funktion grundsätzlich keinen Funktionswert liefern, dann sollte man sie mit dem speziellen Bezeichner void deklarieren; die Funktion entspricht dann einer SUBROUTINE in FORTRAN bzw. einer PROCEDURE in PASCAL

Beispielprogramm p6-4.c


  /*
   *      p6-4.c
   *      Beispielprogramm 4, Abschnitt 6
   *      Ausgabe eines Hilfetextes
   */

  #include <stdio.h>

  void help(int nr)
  { 
    switch (nr) {
      case 1:
	printf("Hilfe 1\n");
	break;
      case 2:
	printf("Hilfe 2\n");
	break;
      case 3:
	printf("Hilfe 3\n");
	break;
      default:
	printf("Fehlerhafter Aufruf von HELP\n");
    }
 
  } /* help() */

- in ANSI-C sind bei einer Funktionsdeklaration außer dem Typ der Funktion auch die Typen der Parameter (ohne ihre Namen) in der Parameterliste anzugeben (sog. Prototyping):


  float umfang(float);
  double flaeche(float);
  int funcbei(int, float, double);
  void proc(void);

- nach einer solchen Deklaration der Parametertypen sind z.B. der ANSI C-Compiler oder das Prüfprogramm lint in der Lage, bei jedem Funktionsaufruf die richtigen Typen der verwendeten aktuellen Parameter sowie deren Anzahl abzuprüfen. Eine parameterlose Funktion wird mit dem Platzhalter void in der Parameterliste gekennzeichnet


6.3 Parameterübergabe

- alle Parameter werden "by value" übergeben, d.h. eine Kopie des Parameterwertes wird übergeben

- das bedeutet, daß Funktionen ihre Parameter nur innerhalb der Funktion, aber nicht im aufrufenden Programm ändern können

Beispielprogramm p6-5.c


  /*
   *      p6-5.c
   *      Beispielprogramm 5, Abschnitt 6
   *      Beispiel fuer Parameteruebergabe und die
   *      Aenderbarkeit von Parametern in Funktionen
   */

  #include <stdio.h>

  void main(void)
  {
    float radius, r;
    int i;
    float neurad(int, float);
      
    i = 10;
    radius = 5.23;
    printf("Vor Aufruf:\t\tI=%2d\tRADIUS=%6.2f\n", i, radius);
    r = neurad(i, radius);
    printf("Nach dem Aufruf:\tI=%2d\tRADIUS=%6.2f\n", i, radius);
    printf("Aber Funktionswert:\t\tR=     %6.2f\n", r);

  } /* main() */

  float neurad(int i, float rad) 
  { 
    i = 0;
  
    rad = rad * 2;
    printf("In der Funktion:\tI=%2d\tRADIUS=%6.2f\n", i, rad);
    return (rad);
  
  } /* neurad() */

- der Lauf des Programm führt zu folgender Ausgabe:


  Vor Aufruf:             I=10    RADIUS=  5.23
  In der Funktion:        I= 0    RADIUS= 10.46
  Nach dem Aufruf:        I=10    RADIUS=  5.23
  Aber Funktionswert:             R=      10.46

- sollen Parameter in der Funktion dauerhaft geändert werden können, so müssen Adressen der zu ändernden Objekte übergeben werden

- wird ein Feld (oder Vektor) als Parameter übergeben, so wird die Adresse des Feldes, genauer die Adresse des ersten Elementes des Feldes übergeben (deshalb funktioniert z.B. strcat (siehe Beispielprogramm p4-2.c))

Beispielprogramm P6-6.c


  /*
   *      p6-6.c
   *      Beispielprogramm 6, Abschnitt 6
   *      Beispiel fuer Parameteruebergabe und die
   *      Aenderbarkeit von Parametern in Funktionen
   *      Anwendung von Pointern
   */

  #include <stdio.h>

  void main(void)
  {
    float radius, r;
    int i;
    float neurad(int *, float *);
  
    i = 10;
    radius = 5.23;
    printf("Vor Aufruf:\t\tI=%2d\tRADIUS=%6.2f\n", i, radius);
    r = neurad(&i, &radius);
    printf("Nach dem Aufruf:\tI=%2d\tRADIUS=%6.2f\n", i, radius);
    printf("Aber Funktionswert:\t\tR=%6.2f\n", r);

  } /* main() */

  float neurad(int *i, float *rad) 
  { 
    *i = 0;
    *rad = *rad * 2;
    printf("In der Funktion:\tI=%2d\tRADIUS=%6.2f\n", *i, *rad);
    return (*rad);

  } /* neurad() */

- der Programmlauf führt zu folgender Ausgabe:


  Vor Aufruf:             I=10    RADIUS=  5.23
  In der Funktion:        I= 0    RADIUS= 10.46
  Nach dem Aufruf:        I= 0    RADIUS= 10.46
  Aber Funktionswert:             R=      10.46


6.4 Speicherklassen und Geltungsbereiche von Namen

- wir haben bisher an verschiedenen Stellen schon einmal die 4 Speicherklassen extern, auto, static und register flüchtig kennengelernt

- im Zusammenhang mit Funktionen ist jetzt ein guter Zeitpunkt, diese Kenntnisse zu vertiefen

1. Speicherklasse auto

- alle Variablen, denen nicht explizit eine Speicherklasse zugewiesen wird und die nicht außerhalb von Funktionen vereinbart werden, fallen in die Speicherklasse auto

- man kann einer Variablen explizit die Speicherklasse auto zuordnen, indem man vor die Typangabe bei der Variablenvereinbarung das Schlüsselwort auto setzt

- automatische Variable werden bei jedem Funktionsaufruf neu erzeugt und beim Verlassen der Funktion wieder zerstört

- daher ist ihr Geltungsbereich auf die lokale Funktion, in der sie vereinbart wurden, beschränkt

- dies gilt auch für Blöcke (Variablen, die innerhalb von Blöcken vereinbart werden und die in die Speicherklasse auto fallen, sind nur lokal in diesem Block bekannt)

- auto Variablen können beliebig (nicht nur mit Konstanten) initialisiert werden

- nicht initialisierte auto Variablen haben einen undefinierten Wert (es existiert kein Weglasswert!)

- auto-Vektoren können nicht initialisiert werden

2. Speicherklasse extern

- alle Objekte, die außerhalb von Funktionen vereinbart werden, sind in der Speicherklasse extern

- die Funktionsnamen selbst sind ebenfalls in der Speicherklasse extern

- externe Objekte sind ab ihrer Vereinbarung bis zum Ende des Quellencodes und sogar in anderen Quellencodedateien bekannt

- bei der Vereinbarung eines Objektes als extern kann es sich um eine Definition oder um eine Deklaration handeln

- die folgenden Vereinbarungen (immer außerhalb von Funktionen) unterscheiden sich wesentlich:


  int sp = 0;             /* Definition */
  double vector[N];
  
  extern int sp;          /* Deklaration */
  extern double vector[];

- bei einer Definition wird eine Variable erzeugt

- nur hier ist eine Initialisierung möglich

- fehlt eine Initialisierung, so wird der Weglasswert 0 eingesetzt

- bei einer Deklaration werden nur die Variableneigenschaften festgelegt, die Variable selbst muß an anderer Stelle im Quellencode definiert sein

- Deklarationen dürfen für eine bestimmte Variable mehrmals im (gesamten) Quellencode vorkommen, eine Definition aber nur einmal

Beispielprogramm p6-7.c


  /*
   *      p6-7.c
   *      Beispielprogramm 7, Abschnitt 6
   */

  #include <stdio.h>

  #define BUFSIZE 100

  char buff[BUFSIZE];     /* Puffer fuer ungetch() */
  int bufp = 0;           /* naechste freie Position */

  /* (evtl. zurueckgest.) Zeichen holen */
  int getch()
  { 
    return ((bufp > 0) ? buff[--bufp] : getchar());
  
  } /* getch() */ 

  /* Zeichen zurueckstellen */
  int ungetch(int c)
  { 
    if (bufp > BUFSIZE)
      printf("ungetch: Zuviele Zeichen\n");
    else
      buff[bufp++] = c;
    
  } /* ungetch() */

3. Speicherklasse static

- static Objekte können sowohl intern als auch extern sein

- static Objekte innerhalb von Funktionen sind nur lokal bekannt, behalten im Gegensatz zu auto Objekten aber ihre Werte zwischen den Funktionsaufrufen bei

- bzgl. der Initialisierung gilt dasselbe wie für externe Objekte, static Vektoren sind daher initialisierbar

- Zeichenketten innerhalb von Funktionen sind immer in der Speicherklasse static (z.B. printf()-Parameterstring)

- static Objekte außerhalb von Funktionen sind externe Objekte, deren Namen aber nur in dieser Quellcodedatei bekannt ist

- Beispiele zur Initialisierung eines Vektors:


  static int ndigit[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
  char string[] = "Dies ist ein String";

4. Speicherklasse register

- in dieser Speicherklasse können sich einfache Variable befinden

- sie entspricht in ihren sonstigen Eigenschaften der Speicherklasse auto

- der Compiler versucht register Variablen so zu verwenden, daß sie in einem wirklichen Hardwareregister der CPU gehalten werden

- ist dies nicht möglich, so wird register ignoriert und die Variable wie auto behandelt


6.5 Rekursive Funktionen

- Funktionen dürfen zwar nicht geschachtelt sein, aber sie können sich selbst direkt oder indirekt aufrufen

- bei jedem Funktionsaufruf werden neue eigene lokale Variablen erzeugt (außer bei der Speicherklasse static)

- viele Probleme lassen sich kompakt und elegant mittels Rekursion lösen

Beispielprogramm p6-8.c


  /*
   *      p6-8.c
   *      Beispielprogramm 8, Abschnitt 6
   *      n dezimal ausgeben
   *      Ohne Rekursion
   */

  #include <stdio.h>

  void printd(int n)
  { 
    char s[10];
    int i;
  
    /* Vorzeichen ausgeben */  
    if (n < 0) {
      putchar('-');
      n = -n;
    }
    /* naechstes Zeichen holen und durch Division entfernen */
    i = 0;
    do {
      s[i++] = n % 10 + '0';
    } while ((n /= 10) > 0);
    /* in umgekehrter Reihenfolge ausgeben */
    while (--i >= 0)
      putchar(s[i]);
    
  } /* printd() */

Beispielprogramm p6-9.c


  /*
   *      p6-9.c
   *      Beispielprogramm 9, Abschnitt 6
   *      n dezimal ausgeben
   *      Mit Rekursion
   */

  #include <stdio.h>

  void printd(int n) 
  { 
    int i;
  
    /* Vorzeichen */
    if (n < 0) {
      putchar('-');
      n = -n;
    }
    /* printd rekursiv aufrufen */  
    if ((i = n / 10) != 0)
      printd(i);          
    putchar(n % 10 + '0');

  } /* printd() */

Beispielprogramm p6-10.c


  /*
   *      p6-10.c
   *      Beispielprogramm 10, Abschnitt 6
   *      Fakultaet berechnen
   */

  long fak(int n)
  { 
    if (n <= 1)
      return(1);
    else
      return(n * fak(n - 1));

  } /* fak() */


zurück zum Inhaltsverzeichnis

 

11. November 1999, Peter Klingebiel, DVZ