Zeiger (1) Allgemeines In C häufig verwendet zur Realisierung mancher Programmierkonzepte Sehr enge Verknüpfung von Zeigern und Feldern Vielseitiges Hilfsmittel, birgt allerdings auch Gefahren für Programmierfehler Symbolisches Beispiel: Visitenkarte ist Zeiger auf Wohnort einer Person Technisch ist ein Zeiger eine Variable mit einer Adresse als Inhalt ptr v Ptr ist die Zeigervariable, die die Adresse der variable v beinhaltet und somit auf diese zeigt: ptr v 500 9 300 500
Zeiger (2) Allgemeines Woher weiß man, wo Variable im Speicher steht? Adressoperator & ptr = &v; Adresse der Variablen v wird ptr zugewiesen Der Programmierer muss nicht wissen, wo v tatsächlich im Speicher steht Ptr zeigt auf v: ptr v 9 Adressoperator kann nur auf Lvalues angewendet werden, nicht auf Ausdrücke, Konstanten oder Register-Variablen
Zeiger (3) Allgemeines Will man das Objekt erreichen, auf den ein Zeiger zeigt, muss man den unären Inhaltsoperator * dem Zeiger vorangestellt werden *ptr = 3; dem Objekt, auf das ptr zeigt, wird der Wert 3 zugewiesen Die Operatoren * und & kehren einander um, also ist *&v äquivalent zu v und &*ptr ist äquivalent zu ptr Zeiger werden wie folgt definiert: Typ *Zeiger; Damit gibt es noch keinen Ort und keinen Speicherplatz für die Variable, sondern nur einen Zeiger, der auf eine Variable zeigen kann Analogie: eine Visitenkarte zeigt die Adresse einer Person, das Stück Papier ist jedoch nicht die Person selbst Zeiger haben einen Datentyp, auf den sie zeigen. Beispiel: long *ptr;
Zeiger (4) Allgemeines Demonstrationsbeispiel: long *ptr; long v; long feld[3]; v = 0; ptr = &v; // Zeige auf v *ptr = 5; // Überschreibe Objekt, auf das ptr zeigt, mit 5 feld[0] = 2; ptr = &feld[0]; // Setze Adresse von feld[0] *ptr = 3; // Überschreibe Objekt, auf das ptr zeigt, mit 3 Normalerweise würde man hier vereinfacht wie folgt implementieren: v = 5; // Überschreibt v mit 5 feld[0] = 3; // Überschreibt feld[0] mit 3
Zeiger (5) Allgemeines Zeiger, die auf kein Objekt zeigen, sind ungültig Um das auszusagen, lässt man sie auf 0 zeigen Auf Adresse 0 befindet sich das Betriebssystem und darauf darf nicht zugegriffen werden Zum Prüfen der Gültigkeit eines Zeigers vergleicht man sie mit 0 long feld[3]; long *ptr = &feld[0]; // ptr ist gesetzt // ptr darf verwendet werden //... ptr = 0; // ptr ist ungültig! // ptr darf nicht mehr verwendet werden! //...
Zeiger (6) Allgemeines Zeiger sind Variablen, man kann mit ihnen auch genauso arbeiten long a; long *ap, *ptr; ap = &a; ptr = ap; ap und ptr zeigen auf die gleiche Adresse, man kann also das gleiche Objekt bearbeiten Man kann mit Zeigern auch rechnen: ptr = ptr + 1; Da Zeiger in C typisiert sind (z.b. long), zeigt der Zeiger nach der Operation auf das nachfolgende Element im Speicher dahinter (vorausgesetzt, da steht auch etwas Sinnvolles drin)
Zeiger (7) Zeiger als Parameter Funktionen liefern nur einen Ergebniswert zurück Sollen mehrere Ergebnisse zurückgegeben werden, müssen Zeiger als Parameter verwendet werden Zeiger zeigen dann auf die Originaldaten, die Zeiger selbst sind dann eine lokale Kopie der Originalzeiger und nur innerhalb der Funktion sichtbar void SwapLong(long *x, long *y) { long h; // Dreieckstausch h = *x; *x = *y; *y = h; } // end SwapLong Aufruf der Funktion: long a, b; SwapLong(&a, &b);
Zeiger (8) Zeiger als Parameter void MinimumSuche(long feld[], long len) { long i, j, min; for (j = 0; j < len; j = j + 1) { // Suche das Minimum // Minimum vor Suchbeginn bei Index 0 min = j; for (i = j + 1; i < len; i = i + 1) if (feld[i] < feld[min]) min = i; // Tausche SwapLong(&feld[j], &feld[min]); } } // end Minimum Funktion SwapLong kann Variablen des Feldes nicht sehen Übergebene Adressen erlauben Zugriff auf die zu tauschenden Elemente
Zeiger (9) Zeiger und Felder Ein Feld ist ein Verbundtyp von Variablen und steht in einem zusammenhängenden Bereich im Speicher Name des Feldes ist ein konstanter Zeiger, der auf die Anfangsadresse des Feldes zeigt Adressen von Feldnamen kann man nicht verändern: long feld[10]; long *ptr; ptr = feld; // Gültig, aber schlechter Programmierstil! feld = ptr; // Fehler! Die Adresse des Feldes ist nicht veränderbar! Leichter zu lesen ist folgender Ausdruck, der die Adresse des ersten Feldelementes zuweist: ptr = &feld[0]; ptr feld
Zeiger (10) Zeiger und Felder Vorteil von Zeigern ist ihre Flexibilität Zum Austausch von Feldern müsste man eigentlich elementweise austauschen Mit Zeigern geht das einfacher: long feld1[10]; long feld2[10]; ptr1 = &feld1[0]; ptr2 = &feld2[0]; ptr2 = &feld1[0]; // die Zeiger werden ausgetauscht zugewiesen ptr1 = &feld2[0]; Entsprechend kann man eine Swap-Funktion schreiben, die den Austausch der Zeiger realisiert
Zeiger (11) Zeigerarithmetik long feld[10]; // Feld mit 10 Elementen vom Typ long long *ptr; // Zeiger auf long ptr = &feld[0]; // ptr zeigt jetzt auf das erste Feldelement Folgende sind Ausdrücke dem Wert nach äquivalent: &feld[0] // Adresse von Felselement 0 ptr Es gilt dann auch folgendes: &feld[n] ptr + n // Adresse von Felselement n // Zeiger um n long-größen erhöhen Erhöhung des Zeigers um 1 bewirkt auch Erhöhung der Adresse um die Größe des Objektes, auf den er zeigt (Dualität Zeiger / Feld) Man kann also mit Zeigern rechnen: ptr = ptr + 1; // Adresse des nächsten long-elementes zuweisen
Zeiger (12) Zeigerarithmetik: Addition Veranschaulichung der Operation ptr = ptr + 1;: ptr feld Die Adressrechnung kann man mit Formeln wie folgt ausdrücken: AA pppppp+1 = AA pppppp + nn DDDDDDDDDDDDDDDD mit nn DDDDDDDDDDDDDDDD = sizeof DDDDDDDDDDDDDDDD Der Datentyp kann beliebig sein (auch benutzerdefiniert) Eine long-variable umfasst auf 32-Bit-Rechnerarchitekturen 4 Bytes, also wird die tatsächliche Adresse des Zeigers um 4 Bytes erhöht Zeigte z.b. der Zeiger vor der Operation auf Adresse 2000 hex, so zeigt er nach der Operation auf Adresse 2004 hex.
Zeiger (13) Zeigerarithmetik: Subtraktion, Vergleiche Will man feststellen, wie viele Objekte zwischen zwei Zeigern liegen, kann man einfach die Differenz bilden: n = ptr2 - ptr1; // Anzahl Objekte zwischen zwei Zeigern ptr2 ptr1 feld Feldelemente werden mit aufsteigender Adresse gespeichert. Demzufolge muss ptr1 von ptr2 subtrahiert werden Multiplikation und Division von Adressen ist nicht erlaubt Vergleichsoperatoren <, <=, >=, >, == und!= können verwendet werden Beispielsweise liefert ptr2 > ptr1 den Wert 1 und somit wahr.
Zeiger (14) Felder von Zeigern Gutes Mittel bei Feldern unterschiedlicher Länge Mehrdimensionale Felder: alle Teilfelder müssen gleiche Länge haben Häufige Anwendung: Feld von Zeigern auf Zeichenketten p p[0] p[1] p[2] a1 a2 a3 long a1[6]; long a2[3]; long a3[5]; long *p[3]; Initialisierung kann entweder durch Zuweisung oder gleich bei der Definition des Feldes erfolgen p[0] = &a1[0]; p[1] = &a2[0]; p[2] = &a3[0]; long *p[3] = {&a1[0], &a2[0], &a3[0]};
Zeiger (15) Felder von Zeigern Will man auf das 3. Element mit Index 2 des ersten Teil-Feldes zugreifen, so geht folgendes a1[2] p[0][2] Der letzte Ausdruck ist (trotz Anschein) kein zweidimensionales Feld Man müsste, um das erkennbar zu machen, eigentlich folgendes schreiben: *(p[0] + 2) Zu jedem Teil-Feld muss man noch die Länge verwalten, was mit einem zweiten Feld möglich ist Ausnahme: Zeichenketten. Diese sind null-terminiert.
Zeiger (16) Felder von Zeigern Effizientes Sortieren lässt sich realisieren, indem man nur Zeiger auf Felder bzw. Zeichenketten austauscht. p p[0] a1 p[1] p[2] a2 a3 Nach dem Vergleich reicht es aus, die Zeiger auszutauschen long *ptr1; // Zeiger long *ptr2; long a1[6]; // Felder long a2[3]; //... ptr1 = &a1[0]; // Init ptr2 = &a2[0]; //... SwapZeiger(&ptr1, &ptr2); // Parameter sind Adressen der Zeiger, // also Zeiger auf Zeiger void SwapZeiger(long **p1, long **p2) { long *h; // Dreieckstausch h = *p1; *p1 = *p2; *p2 = h; } // end SwapZeiger
Zeiger (17) Zeiger auf Zeiger Beispiel von letzter Folie benutzt einen Zeiger auf einen Zeiger als Parameter, damit der Original-Zeiger für den Tausch manipuliert werden kann Dreieckstausch ist der gleich geblieben, wie bereits im Abschnitt Felder gezeigt Da Zeiger getauscht werden sollen, muss man auf diese zeigen Folgendes nicht empfohlene Beispiel veranschaulicht höhere Zeiger : long feld[6]; // Feld mit 6 Elementen long *ptr1 = &feld[0]; // Zeiger auf das Feld long **ptr2 = &ptr1; // Zeiger auf ptr1 long ***ptr3 = &ptr2; // Zeiger auf ptr2 ptr3 ptr2 ptr1 feld
Zeiger (18) Konstante Zeiger Selbstbeschränkung ist wichtig, um nicht versehentlich unveränderbare Daten zu überschreiben, wie beispielsweise hier möglich: char *ptr; Ein wichtiges Mittel ist, Speicher, auf den der Zeiger zeigt, als konstant zu definieren: const char *ptr; ptr zeigt auf ein Objekt vom Typ char, das konstant ist Falls der Zeiger selbst nicht modifiziert werden darf, sieht das so aus: char * const ptr = &feld[0]; Da der Zeiger nicht modifiziert werden darf, muss er gleich mitinitialisiert werden Soll sowohl der Zeiger als auch das gezeigte Element konstant sein, muss das Schlüsselwort const an beiden Stellen stehen const char * const ptr = &feld[0];