Funktionen & Scope
Funktionen in C: Deklaration vs. Definition, Prototypen in Headern, Pass-by-Value und wie du sauberen C-Code modular baust.
Inhaltsverzeichnis
Funktionen & Scope in C
C-Funktionen sind schlank und direkt - aber die Trennung von Deklaration (Prototyp) und Definition ist etwas, das du in modernen Sprachen nicht mehr kennst.
Grundform
int addiere(int a, int b) {
return a + b;
}
int main(void) {
int summe = addiere(3, 4);
printf("%d\n", summe);
return 0;
}
- Rueckgabetyp vor dem Namen
- Parameter brauchen Typ und Namen
- Semikolons am Ende jedes Statements
void - keine Rueckgabe
void gruessen(const char *name) {
printf("Hallo, %s!\n", name);
}
Funktions-Prototypen
Wenn die Definition erst nach dem Aufruf steht, brauchst du einen Prototyp:
#include <stdio.h>
// Prototyp
int addiere(int a, int b);
int main(void) {
printf("%d\n", addiere(3, 4)); // nutzt Prototyp
return 0;
}
// Definition
int addiere(int a, int b) {
return a + b;
}
Das ist wichtig, weil C der Reihe nach liest. Ohne Prototyp haette der Compiler vor der Definition keine Info ueber die Signatur.
In der Praxis stehen Prototypen in Header-Dateien (.h), damit mehrere Quelldateien sie teilen koennen.
Header und Source trennen
math_utils.h:
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
int addiere(int a, int b);
int subtrahiere(int a, int b);
#endif
Die #ifndef / #define / #endif-Struktur ist ein Include-Guard - er verhindert, dass der Header doppelt eingelesen wird.
math_utils.c:
#include "math_utils.h"
int addiere(int a, int b) {
return a + b;
}
int subtrahiere(int a, int b) {
return a - b;
}
main.c:
#include <stdio.h>
#include "math_utils.h"
int main(void) {
printf("%d\n", addiere(3, 4));
printf("%d\n", subtrahiere(10, 4));
return 0;
}
Kompilieren:
gcc main.c math_utils.c -o app
Pass-by-Value
C uebergibt Argumente per Kopie (Pass-by-Value):
void verdoppeln(int x) {
x *= 2; // aendert nur die Kopie
}
int main(void) {
int y = 5;
verdoppeln(y);
printf("%d\n", y); // immer noch 5!
return 0;
}
Pass-by-Reference via Pointer
Wenn die Funktion den Wert aendern soll, uebergibst du einen Pointer:
void verdoppeln(int *x) {
*x *= 2; // Dereferenzieren und aendern
}
int main(void) {
int y = 5;
verdoppeln(&y); // Adresse uebergeben
printf("%d\n", y); // jetzt 10
return 0;
}
Das &y uebergibt die Adresse, *x liest/schreibt den Wert an dieser Adresse.
Arrays als Parameter
Arrays werden immer als Pointer uebergeben. Du brauchst die Groesse separat:
int summe(const int *zahlen, size_t n) {
int total = 0;
for (size_t i = 0; i < n; i++) {
total += zahlen[i];
}
return total;
}
int main(void) {
int werte[] = {1, 2, 3, 4, 5};
size_t n = sizeof(werte) / sizeof(werte[0]);
printf("%d\n", summe(werte, n)); // 15
return 0;
}
Die alternative Schreibweise int zahlen[] ist gleichwertig zu int *zahlen - C behandelt Array-Parameter wie Pointer:
int summe(const int zahlen[], size_t n) { /* gleich */ }
Strings als Parameter
#include <string.h>
size_t laenge(const char *text) {
return strlen(text);
}
int main(void) {
printf("%zu\n", laenge("Hallo")); // 5
return 0;
}
const char * signalisiert: โIch รคndere den String nicht.โ
Mehrere Rueckgabewerte
C hat keine Multi-Return. Zwei gelaeufige Muster:
Out-Parameter via Pointer
void min_max(const int *zahlen, size_t n, int *min_out, int *max_out) {
*min_out = zahlen[0];
*max_out = zahlen[0];
for (size_t i = 1; i < n; i++) {
if (zahlen[i] < *min_out) *min_out = zahlen[i];
if (zahlen[i] > *max_out) *max_out = zahlen[i];
}
}
int main(void) {
int zahlen[] = {3, 1, 4, 1, 5, 9, 2, 6};
int mn, mx;
min_max(zahlen, 8, &mn, &mx);
printf("min=%d max=%d\n", mn, mx);
return 0;
}
Struct als Rueckgabe
struct MinMax {
int min;
int max;
};
struct MinMax min_max(const int *zahlen, size_t n) {
struct MinMax result = {zahlen[0], zahlen[0]};
for (size_t i = 1; i < n; i++) {
if (zahlen[i] < result.min) result.min = zahlen[i];
if (zahlen[i] > result.max) result.max = zahlen[i];
}
return result;
}
Strukturen lernen wir gleich im Detail kennen.
static-Funktionen
Eine Funktion, die nur in der aktuellen Datei sichtbar sein soll:
static int hilfsfunktion(int x) {
return x * 2;
}
Gleich wie static-Variablen: beschraenkt den Scope auf die Uebersetzungseinheit (.c-Datei). Hilft, Namenskonflikte zu vermeiden.
Rekursion
int fakultaet(int n) {
if (n <= 1) return 1;
return n * fakultaet(n - 1);
}
int main(void) {
printf("%d\n", fakultaet(5)); // 120
return 0;
}
Wie in anderen Sprachen: Achte auf die Abbruchbedingung, sonst Stack Overflow.
Praktisches Beispiel
string_utils.h:
#ifndef STRING_UTILS_H
#define STRING_UTILS_H
#include <stddef.h>
int zaehle_vokale(const char *text);
void zu_gross(char *text);
#endif
string_utils.c:
#include "string_utils.h"
#include <ctype.h>
int zaehle_vokale(const char *text) {
int anzahl = 0;
for (size_t i = 0; text[i] != '\0'; i++) {
char c = (char) tolower((unsigned char) text[i]);
if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
anzahl++;
}
}
return anzahl;
}
void zu_gross(char *text) {
for (size_t i = 0; text[i] != '\0'; i++) {
text[i] = (char) toupper((unsigned char) text[i]);
}
}
main.c:
#include <stdio.h>
#include "string_utils.h"
int main(void) {
char name[] = "Programmieren";
printf("Vokale: %d\n", zaehle_vokale(name));
zu_gross(name);
printf("Gross: %s\n", name);
return 0;
}
Zusammenfassung
typ name(typ param) { return wert; }ist die Grundstruktur- Prototypen in Headern, Definitionen in
.c-Dateien - Include-Guards schuetzen vor Doppelinklude
- Pass-by-Value ist Standard - Pointer fuer โaendernโ
- Arrays werden immer als Pointer uebergeben - Groesse separat mitfuehren
- Mehrere Rueckgabewerte ueber Out-Parameter oder Structs
static-Funktionen bleiben in der Datei
Im naechsten Kapitel kommen wir zum Herzstueck: Pointer und Arrays.