Fortgeschrittene Algorithmen und Programmierung
Ein Zeiger (Pointer) ist eine Variable, die die Speicheradresse einer anderen Variable speichert.
Statt direkt mit dem Wert zu arbeiten, arbeiten wir mit der Adresse,
an der der Wert im Speicher liegt.
Wenn Sie eine Variable deklarieren, reserviert der Computer Speicherplatz.
Dieser Speicherplatz hat eine eindeutige Adresse.
int zahl = 42;
| Variable | Speicheradresse | Wert |
|---|---|---|
| zahl | 0x7fff5fbff8ac | 42 |
Die Variable zahl hat den Wert 42
und liegt an der Adresse 0x7fff5fbff8ac im Speicher.
Syntax: datentyp *zeigerName;
int *zeiger; // Zeiger auf einen int-Wert double *preisZeiger; // Zeiger auf einen double-Wert char *buchstabe; // Zeiger auf einen char-Wert
int *zeiger bedeutet: "zeiger ist ein Zeiger auf int"
Achtung: Ein nicht initialisierter Zeiger zeigt auf eine zufällige Adresse!
Verwenden Sie immer initialisierte Zeiger.
Der & Operator gibt die Adresse einer Variable zurück.
int zahl = 42; int *zeiger; zeiger = &zahl; // zeiger erhält die Adresse von zahl printf("Wert von zahl: %d\n", zahl); // 42 printf("Adresse von zahl: %p\n", &zahl); // z.B. 0x7fff5fbff8ac printf("Wert von zeiger: %p\n", zeiger); // z.B. 0x7fff5fbff8ac
Der * Operator greift auf den Wert zu, auf den ein Zeiger zeigt.
int zahl = 42; int *zeiger = &zahl; // zeiger zeigt auf zahl printf("Wert von zahl: %d\n", zahl); // 42 printf("Wert über Zeiger: %d\n", *zeiger); // 42 *zeiger = 100; // Ändert den Wert von zahl auf 100! printf("Neuer Wert von zahl: %d\n", zahl); // 100
int *zeiger; → Zeiger-Variable*zeiger = 100; → Dereferenzierung
Wichtig: *zeiger greift auf den Wert an der Adresse zu,
die in zeiger gespeichert ist!
#include <stdio.h> int main() { double preis = 19.99; double *preisZeiger; preisZeiger = &preis; // Adresse von preis speichern printf("Preis: %.2f\n", preis); // 19.99 printf("Adresse: %p\n", &preis); // z.B. 0x7fff... printf("Zeiger Wert: %p\n", preisZeiger); // gleiche Adresse printf("Wert über Zeiger: %.2f\n", *preisZeiger); // 19.99 *preisZeiger = 24.99; // preis ändern über Zeiger printf("Neuer Preis: %.2f\n", preis); // 24.99 return 0; }
double preis = 19.99; double *preisZeiger = &preis;
int *zeiger; // Zeigt auf zufällige Adresse! *zeiger = 42; // ❌ GEFÄHRLICH! Kann abstürzen!
int zahl = 10; int *zeiger; zeiger = zahl; // ❌ FALSCH! zahl ist kein Zeiger zeiger = &zahl; // ✅ RICHTIG!
int zahl = 10; int *zeiger = &zahl; printf("%d", zeiger); // ❌ Gibt Adresse aus, nicht Wert printf("%d", *zeiger); // ✅ Gibt Wert aus (10)
Ein Null-Zeiger ist ein Zeiger, der auf nichts zeigt.
Er wird mit NULL initialisiert.
#include <stdio.h> #include <stdlib.h> // für NULL int main() { int *zeiger = NULL; // Zeiger auf nichts if (zeiger == NULL) { printf("Zeiger ist NULL\n"); } // Vor Verwendung prüfen! if (zeiger != NULL) { printf("%d\n", *zeiger); } return 0; }
NULL oder einer gültigen AdresseNULL vor Dereferenzierung| Operation | Syntax | Bedeutung |
|---|---|---|
| Deklaration | int *zeiger; | Zeiger auf int deklarieren |
| Adresse nehmen | zeiger = &zahl; | Adresse von zahl holen |
| Dereferenzieren | *zeiger = 42; | Wert an Adresse ändern |
| Null-Zeiger | zeiger = NULL; | Zeiger auf nichts setzen |
& = "Adresse von" (Address-of)
* = "Wert an Adresse" (Value-at)
Wenn wir eine Variable an eine Funktion übergeben,
wird nur eine Kopie übergeben (Call-by-Value).
Änderungen in der Funktion wirken sich nicht auf die Original-Variable aus!
Mit Zeigern können wir die Adresse übergeben.
So kann die Funktion die Original-Variable ändern (Call-by-Reference).
#include <stdio.h> void aendernUeberWert(double wert1, double wert2) { wert1 = wert1 * 1.5; wert2 = wert2 * 2.0; printf("In Funktion: %.2f, %.2f\n", wert1, wert2); } int main() { double preis1 = 10.0; double preis2 = 20.0; aendernUeberWert(preis1, preis2); printf("In main: %.2f, %.2f\n", preis1, preis2); return 0; }
Problem: Die Änderungen in der Funktion bleiben in der Funktion!
preis1 und preis2 in main() bleiben unverändert.
#include <stdio.h> void aendernUeberZeiger(double *zeiger1, double *zeiger2) { *zeiger1 = *zeiger1 * 1.5; *zeiger2 = *zeiger2 * 2.0; printf("In Funktion: %.2f, %.2f\n", *zeiger1, *zeiger2); } int main() { double preis1 = 10.0; double preis2 = 20.0; aendernUeberZeiger(&preis1, &preis2); // Adressen übergeben! printf("In main: %.2f, %.2f\n", preis1, preis2); return 0; }
Erfolg! Die Änderungen wirken sich auf die Original-Variablen aus!
void funktion(int wert) { wert = 42; // ändert nur Kopie } int main() { int x = 10; funktion(x); // x ist noch 10 }
void funktion(int *zeiger) { *zeiger = 42; // ändert Original } int main() { int x = 10; funktion(&x); // x ist jetzt 42 }
Eine Funktion kann normalerweise nur einen Wert zurückgeben.
Mit Zeigern können wir mehrere Werte "zurückgeben":
#include <stdio.h> void berechneKreis(double radius, double *umfang, double *flaeche) { *umfang = 2 * 3.14159 * radius; *flaeche = 3.14159 * radius * radius; } int main() { double u, f; berechneKreis(5.0, &u, &f); printf("Umfang: %.2f\n", u); // 31.42 printf("Fläche: %.2f\n", f); // 78.54 return 0; }
Aufgabe: Zwei Variablen vertauschen
#include <stdio.h> void tausche(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } int main() { int x = 5, y = 10; printf("Vorher: x=%d, y=%d\n", x, y); tausche(&x, &y); // Adressen übergeben printf("Nachher: x=%d, y=%d\n", x, y); return 0; }
// Gut: Zeiger-Parameter erkennbar void aendereWert(int *wertZeiger); // Besser: Dokumentation void aendereWert(int *wertZeiger); // ändert *wertZeiger
void sichereAenderung(int *zeiger) { if (zeiger == NULL) { return; // Schutz vor NULL-Zeigern } *zeiger = 42; }
// Zeiger darf Wert nicht ändern void leseWert(const int *zeiger) { printf("%d", *zeiger); // OK // *zeiger = 42; // Fehler! }
Der Name eines Arrays ist ein Zeiger auf das erste Element!
int zahlen[5] = {10, 20, 30, 40, 50}; // Diese sind identisch: printf("%p\n", zahlen); // Adresse des ersten Elements printf("%p\n", &zahlen[0]); // Adresse des ersten Elements // Wert des ersten Elements: printf("%d\n", zahlen[0]); // 10 printf("%d\n", *zahlen); // 10 (gleichwertig!)
zahlen = Adresse des ersten Elements&zahlen[0] = Adresse des ersten Elements*zahlen = Wert des ersten Elementszahlen[0] = Wert des ersten ElementsMan kann mit Zeigern rechnen, um durch Arrays zu navigieren.
int zahlen[5] = {10, 20, 30, 40, 50}; int *zeiger = zahlen; // zeiger zeigt auf zahlen[0] printf("%d\n", *zeiger); // 10 (erstes Element) printf("%d\n", *(zeiger + 1)); // 20 (zweites Element) printf("%d\n", *(zeiger + 2)); // 30 (drittes Element) // Äquivalent zu: printf("%d\n", zahlen[0]); // 10 printf("%d\n", zahlen[1]); // 20 printf("%d\n", zahlen[2]); // 30
zeiger + 1 bedeutet: "Gehe zum nächsten Element"
(nicht zur nächsten Speicheradresse!)
*(zeiger + i) ist dasselbe wie zeiger[i]
| Array-Notation | Zeiger-Notation | Bedeutung |
|---|---|---|
| zahlen[0] | *zahlen | Erstes Element |
| zahlen[1] | *(zahlen + 1) | Zweites Element |
| zahlen[2] | *(zahlen + 2) | Drittes Element |
| zahlen[i] | *(zahlen + i) | Element an Position i |
| &zahlen[i] | zahlen + i | Adresse von Element i |
zahlen[i] und *(zahlen + i) sind identisch!
Wenn wir ein Array an eine Funktion übergeben, wird automatisch ein Zeiger übergeben:
#include <stdio.h> // Beide Schreibweisen sind äquivalent: void ausgabeFeld1(double feld[], int groesse); void ausgabeFeld2(double *feld, int groesse); void ausgabeFeld1(double feld[], int groesse) { for (int i = 0; i < groesse; i++) { printf("%.2f ", feld[i]); } printf("\n"); } int main() { double preise[3] = {1.45, 0.85, 0.75}; ausgabeFeld1(preise, 3); return 0; }
Wichtig: Die Größe des Arrays geht verloren!
Deshalb müssen wir die Größe als separaten Parameter übergeben.
#include <stdio.h> void ausgabeFeld(double *dFeld, int groesse) { printf("Array-Elemente:\n"); for (int i = 0; i < groesse; i++) { // Zeiger-Notation verwenden: printf("Element %d: %.2f\n", i, *(dFeld + i)); } } int main() { double preise[4] = {1.45, 0.85, 0.75, 2.30}; ausgabeFeld(preise, 4); return 0; }
#include <stdio.h> void verdoppleFeld(int *feld, int groesse) { for (int i = 0; i < groesse; i++) { *(feld + i) = *(feld + i) * 2; // oder: feld[i] *= 2; } } int main() { int zahlen[5] = {1, 2, 3, 4, 5}; printf("Vorher: "); for (int i = 0; i < 5; i++) printf("%d ", zahlen[i]); verdoppleFeld(zahlen, 5); printf("\nNachher: "); for (int i = 0; i < 5; i++) printf("%d ", zahlen[i]); return 0; }
Wenn ein Array als Zeiger übergeben wird, geht die Information
über die Größe verloren.
void funktion(int *feld) { // sizeof(feld) gibt NICHT die Array-Größe zurück! // Es gibt nur die Größe des Zeigers zurück (z.B. 8 Bytes) int groesse = sizeof(feld); // ❌ FALSCH! (gibt 8 zurück) } int main() { int zahlen[100]; int groesse = sizeof(zahlen) / sizeof(zahlen[0]); // ✅ OK (100) funktion(zahlen); // Größe geht verloren! }
Übergeben Sie die Größe immer als separaten Parameter!
zahlen ist gleich &zahlen[0]zahlen[i] ist gleich *(zahlen + i)
Verwenden Sie zahlen[i] für bessere Lesbarkeit,
aber verstehen Sie, dass *(zahlen + i) identisch ist.
Erstellen Sie ein Programm, das:
#include <stdio.h> void verarbeiteWerte(double *w1, double *w2, double *w3, double *durchschnitt) { printf("Wert 1: %.2f\n", *w1); printf("Wert 2: %.2f\n", *w2); printf("Wert 3: %.2f\n", *w3); *durchschnitt = (*w1 + *w2 + *w3) / 3.0; } int main() { double wert1, wert2, wert3, durchschnitt; printf("Geben Sie 3 Werte ein:\n"); scanf("%lf %lf %lf", &wert1, &wert2, &wert3); verarbeiteWerte(&wert1, &wert2, &wert3, &durchschnitt); printf("\nDurchschnitt: %.2f\n", durchschnitt); return 0; }
int *zeiger; // Nicht initialisiert - zeigt irgendwohin! *zeiger = 42; // ❌ GEFAHR! Kann System zum Absturz bringen // Besser: int *zeiger = NULL; // oder: int zahl; int *zeiger = &zahl;
int *gefaehrlich() { int temp = 42; return &temp; // ❌ temp existiert nach Funktionsende nicht mehr! }
int zahlen[5]; int *zeiger = zahlen; *(zeiger + 10) = 42; // ❌ Außerhalb des Arrays!
int *zeiger = NULL; // ✅ Gut!
if (zeiger != NULL) { *zeiger = 42; // ✅ Sicher! }
for (int i = 0; i < groesse; i++) { // ✅ Bleibt innerhalb der Grenzen *(feld + i) = 0; }
// Ändert *wert auf das Doppelte void verdopple(int *wert); // ✅ Klar dokumentiert
Regel: Verwenden Sie Zeiger nur wenn nötig,
aber scheuen Sie sich nicht, wenn sie die richtige Lösung sind!
zahlen[i] = *(zahlen + i)