Felder (Arrays) und Zeiger (Pointers) - Teil I Felder: Motivation Wir können jetzt über Zahlen iterieren: for (int i=; i<n; ++i) {... Oft muss man aber über Daten iterieren (Beisiel: finde ein Kino in Zürich, das heute A Quantum of C++ zeigt) Felder dienen zum Seichern von Folgen gleichartiger Daten (Beisiel: Sielläne aller Zürcher Kinos) Felder: erste Anwendung Das Sieb des Eratosthenes o berechnet alle Primzahlen < n o Methode: Ausstreichen der Nicht- Primzahlen Felder: erste Anwendung Das Sieb des Eratosthenes o berechnet alle Primzahlen < n o Methode: Ausstreichen der Nicht- Primzahlen 2 3 4 5 6 7 8 9 1 11 12 13 14 15 16 17 18 2 3 5 7 9 11 13 15 17 Streiche alle echten Vielfachen von 2... Streiche alle echten Vielfachen von 2... 1
Felder: erste Anwendung Das Sieb des Eratosthenes o berechnet alle Primzahlen < n o Methode: Ausstreichen der Nicht- Primzahlen Felder: erste Anwendung Das Sieb des Eratosthenes o berechnet alle Primzahlen < n o Methode: Ausstreichen der Nicht- Primzahlen 2 3 5 7 9 11 13 15 17 2 3 5 7 9 11 13 15 17...und gehe zur nächsten Zahl Streiche alle echten Vielfachen von 3... Felder: erste Anwendung Das Sieb des Eratosthenes o berechnet alle Primzahlen < n o Methode: Ausstreichen der Nicht- Primzahlen Felder: erste Anwendung Das Sieb des Eratosthenes o berechnet alle Primzahlen < n o Methode: Ausstreichen der Nicht- Primzahlen 2 3 5 7 11 13 17 2 3 5 7 11 13 17 Streiche alle echten Vielfachen von 3......und gehe zur nächsten Zahl 2
Felder: erste Anwendung Das Sieb des Eratosthenes o berechnet alle Primzahlen < n o Methode: Ausstreichen der Nicht- Primzahlen Felder: erste Anwendung Das Sieb des Eratosthenes o berechnet alle Primzahlen < n o Methode: Ausstreichen der Nicht- Primzahlen o Frage: wie streichen wir Zahlen aus??? 2 3 5 7 11 13 17 Am Ende des Streichungsrozesses bleiben genau die Primzahlen übrig! mit einem Feld! Felder: Imlementierung Sieb des Eratosthenes int main() { // definition and initialization: rovides us with // Booleans [],..., [] bool [1]; for (unsigned int i = ; i < 1; ++i) [i] = false; // comutation and outut std::cout << "Prime numbers in {2,...,:\n"; for (unsigned int i = 2; i < 1; ++i) if (![i]) { // i is rime std::cout << i << " "; // cross out all roer multiles of i for (unsigned int m = 2*i; m < 1; m += i) [m] = true; std::cout << "\n"; Berechnet alle Primzahlen < 1 Feld: [i] gibt an, ob i schon ausgestrichen wurde Felder: Imlementierung Sieb des Eratosthenes int main() { // definition and initialization: rovides us with // Booleans [],..., [] bool [1]; for (unsigned int i = ; i < 1; ++i) [i] = false; // comutation and outut std::cout << "Prime numbers in {2,...,:\n"; for (unsigned int i = 2; i < 1; ++i) if (![i]) { // i is rime std::cout << i << " "; // cross out all roer multiles of i for (unsigned int m = 2*i; m < 1; m += i) [m] = true; std::cout << "\n"; Berechnet alle Primzahlen < 1 Feld: [i] gibt an, ob i schon ausgestrichen wurde Das Sieb: gehe zur jeweils nächsten nichtgestrichenen Zahl i (diese ist Primzahl), gib sie aus und streiche alle echten Vielfachen von i aus return ; return ; 3
Felder: Definition Deklaration einer Feldvariablen (array): Felder: Definition Deklaration einer Feldvariablen (array): T a [exr ] Wert k ist bei Komilierung bekannt (z.b. Literal) T a [exr ] Wert k ist bei Komilierung bekannt (z.b. Literal) konstanter ganzzahliger Ausdruck; Wert gibt Länge des Feldes an konstanter ganzzahliger Ausdruck; Wert gibt Länge des Feldes an Variable des Feld-Tys Variable des Feld-Tys zugrundeliegender Ty Ty von a : T [k ] Wertebereich von T [k ]: T k zugrundeliegender Ty Beisiel: bool [1] Felder variabler Länge? Praktischer wäre: int main() { // inut of n std::cout << Comute rime numbers in [2, n) for n =? ; unsigned int n; std::cin >> n; // definition and initialization: rovides us with // Booleans [],..., [n-1] bool [n];... Felder variabler Länge? Praktischer (aber nicht erlaubt) wäre: int main() { // inut of n std::cout << Comute rime numbers in [2, n) for n =? ; unsigned int n; std::cin >> n; // definition and initialization: rovides us with // Booleans [],..., [n-1] bool [n]; // Fehler!... kein konstanter Ausdruck! 4
Felder variabler Länge? Praktischer (und erlaubt) wäre: int main() { // inut of n std::cout << Comute rime numbers in [2, n) for n =? ; unsigned int n; std::cin >> n; // definition and initialization: rovides us with // Booleans [],..., [n-1] bool* = new bool[n]; // ok!... so geht es (Erklärung folgt)! Feld-Initialisierung int a[5]; int a[5] = {4, 3, 5, 2, 1; auch ok; Länge wird deduziert Die 5 Elemente von a bleiben uninitialisiert (können säter Werte zugewiesen bekommen) Die 5 Elemente von a werden mit einer Initialisierungsliste initialisiert int a[] = {4, 3, 5, 2, 1; Seicherlayout eines Feldes o Ein Feld belegt einen zusammenhängenden Seicherbereich Beisiel: Feld mit 4 Elementen Wahlfreier Zugriff (Random Access) Der L-Wert a [ exr ] Wert i hat Ty T und bezieht sich auf das i te Element des Feldes a (Zählung ab ) Seicherzellen für jeweils einen Wert vom Ty T a[] a[1] a[2] a[3] 5
Wahlfreier Zugriff (Random Access) a [ exr ] Der Wert i von exr heisst Feldindex []: Subskrit-Oerator Wahlfreier Zugriff (Random Access) Die Deklaration T a [exr ] imlizite Definition des Tys von a kann man deshalb auch lesen als a [exr ] ist vom Ty T Wahlfreier Zugriff (Random Access) o Wahlfreier Zugriff ist sehr effizient (Reduktion auf Adressarithmetik): : Adresse von a s : Seicherbedarf von T (in Zellen) + si : Adresse von a[i] a[i] Wahlfreier Zugriff (Random Access) Warnung: Die Länge n eines Feldes kann nicht abgefragt werden, man muss sie wissen. Für jeden Feldindex i muss i < n gelten, andernfalls ist das Programmverhalten undefiniert! 6
Felder sind nicht selbstbeschreibend o Man kann Felder nicht wie bei anderen Tyen initialisieren und zuweisen: Warum? int a[5] = {4,3,5,2,1; int b[5]; b = a; // Fehler! int c[5] = a; // Fehler! Felder sind nicht selbstbeschreibend o Felder sind Erblast der Srache C und aus heutiger Sicht rimitiv o Ein Feld merkt sich nur die Adresse des ersten Elements bei legalen Indizes! o kein Problem bei Random Access, aber o beim Initialisieren / Zuweisen ist dann nicht klar, wieviele Elemente koiert werden müssen int a[5] = {4,3,5,2,1; int b[5]; b = a; // wieviele Elemente haben a und b nochmal? Felder als Daten-Container Container: o Objekt, das andere Objekte seichern kann... o...und die Möglichkeit anbietet, über die geseicherten Objekte zu iterieren (Kinorogramme...) Iteration in Feldern geht über wahlfreien Zugriff: a[], a[1],...,a[n-1] Iteration durch wahlfreien Zugriff for (unsigned int i = ; i < 1; ++i) [i] = false; Berechnungsaufwand: 7
Iteration durch wahlfreien Zugriff for (unsigned int i = ; i < 1; ++i) [i] = false; Berechnungsaufwand (Adressberechnung): +s Iteration durch wahlfreien Zugriff for (unsigned int i = ; i < 1; ++i) [i] = false; +2s Berechnungsaufwand (Adressberechnung): Iteration durch wahlfreien Zugriff for (unsigned int i = ; i < 1; ++i) [i] = false; +3s Berechnungsaufwand (Adressberechnung): Iteration durch wahlfreien Zugriff for (unsigned int i = ; i < 1; ++i) [i] = false; Pro Feldelement eine Addition Berechnungsaufwand und eine Multilikation (Adressberechnung): + i s 8
Effizientere und natürlichere Iteration Effizientere und natürlichere Iteration +s +2s Addieren von s: gehe zum nächsten Element +s Berechnungsaufwand (Adressberechnung): Pro Feldelement eine Addition vorher + s Effizientere und natürlichere bool* = ; Pro Feldelement eine Addition // ointer to first element bool* end = + 1; // ointer after last element vorher + s Effizientere und natürlichere bool* = ; Pro Feldelement eine Addition // ointer to first element bool* end = + 1; // ointer after last element wird noch genau erklärt! vorher + s 9
Buchlesen: Wahlfreier Zugriff vs. natürliche Iteration Zeiger Wahlfreier Zugriff: o öffne Buch auf S.1 o klae Buch zu o öffne Buch auf S.2-3 o klae Buch zu o öffne Buch auf S.4-5 o... Natürliche Iteration: o öffne Buch auf S.1 o blättere um o blättere um o blättere um o blättere um o... o erlauben das Reräsentieren von und das Rechnen mit Adressen o unterstützen insbesondere die Oeration gehe zum nächsten Element eines Feldes o sind mit Vorsicht zu verwenden (beim Verrechnen mit Adressen stürzt meist das Programm ab) Zeiger-Tyen Zeiger: Visualisierung T * zugrundeliegender Ty srich: Zeiger auf T o T * hat als mögliche Werte Adressen von Objekten des Tys T o Ausdrücke vom Ty T * heissen Zeiger Zeiger auf das Objekt (Wert von ist die Adresse des Objekts) Objekt im Seicher 1
Adressoerator o liefert einen Zeiger (R-Wert) auf ein beliebiges Objekt, gegeben durch einen L-Wert & L-Wert Dereferenzierungsoerator o liefert einen L-Wert für ein Objekt, gegeben durch einen Zeiger auf das Objekt * R-Wert int i = 5; int* itr = &i; itr i & int i = 5; int* itr = &i; int j = *itr; // j = 5 itr i * Dereferenzierungsoerator o liefert einen L-Wert für ein Objekt, gegeben durch einen Zeiger auf das Objekt int i = 5; int* itr = &i; int j = *itr; // j = 5 Kann man lesen als int *itr = &i (d.h. *itr ist vom Ty int ). Wieder ein Fall von imliziter Tydefinition! itr i * Dereferenzierungsoerator = Adressoerator -1 Zeiger (R-Wert) & * Objekt (L-Wert) 11
Nichtinitialisierte Zeiger o sind eine tyische Fehlerquelle int* itr; // nicht initialisiert... int j = *itr; // Objekt an "zufaelliger Adresse // --> undefiniertes Verhalten Nullzeiger o Zeiger, die (noch) nicht auf ein Objekt zeigen, sollten mit initialisiert werden int* itr = ; // Nullzeiger... int j = *itr; // kein Objekt an dieser Adresse // --> "sicherer Laufzeitfehler Nullzeiger o Zeiger, die (noch) nicht auf ein Objekt zeigen, sollten mit initialisiert werden Felder sind Zeiger o Jedes Feld vom Ty T [k] ist in den Ty T* konvertierbar int* itr = ;... if (itr!= ) { int j = *itr;... // Nullzeiger // so ist s am besten Feld-nach-Zeiger-Konversion o Ergebnis der Konversion ist ein Zeiger auf das erste Element (Feldindex ) o Tritt ein Feld in einem Ausdruck auf, so wird es automatisch konvertiert Im Rechner assiert dabei nichts: ein Feld ist ohnehin nur durch die Adresse des ersten Elements reräsentiert. 12
Felder sind Zeiger Zeiger-Arithmetik Beisiel: int a[5]; int* = a; o Zeiger {+, - ganze Zahl o Zeiger {==,!=, <, >, <=, >= Zeiger o Zeiger - Zeiger a Ist das äquivalent zu int a[5]; int* = &a[];? Nicht ganz, denn hier wird zusätzlich noch a[] ausgewertet! : Zeiger auf ein Feldelement a[] a[1] a[2] a[3] a[4] a[5] Zeiger-Arithmetik Zeiger-Arithmetik o Zeiger {+, - ganze Zahl o Zeiger {==,!=, <, >, <=, >= Zeiger o Zeiger - Zeiger o Zeiger {+, - ganze Zahl o Zeiger {==,!=, <, >, <=, >= Zeiger o Zeiger - Zeiger Addenden mit Ergebniszeiger ausserhalb dieses Bereichs sind illegal!...oder auf Zelle direkt hinter dem Feld (ast-the-end Zeiger) - 2-1 + 1 + 2 + 3 + 4 a[] a[1] a[2] a[3] a[4] a[5] a[] a[1] a[2] a[3] a[4] a[5] 13
Zeiger-Arithmetik o Zeiger {+, - ganze Zahl o Zeiger {==,!=, <, >, <=, >= Zeiger o Zeiger - Zeiger +=, -=, ++, -- gibt es auch, mit der üblichen Bedeutung Zeiger-Arithmetik o Zeiger {+, - ganze Zahl o Zeiger {==,!=, <, >, <=, >= Zeiger o Zeiger - Zeiger!= : nicht == <= : < oder == q < == < r > : nicht <= >= : nicht < a[] a[1] a[2] a[3] a[4] a[5] a[] a[1] a[2] a[3] a[4] a[5] Zeiger-Arithmetik o Zeiger {+, - ganze Zahl o Zeiger {==,!=, <, >, <=, >= Zeiger o Zeiger - Zeiger -2 Zeiger-Arithmetik o Zeiger {+, - ganze Zahl o Zeiger {==,!=, <, >, <=, >= Zeiger o Zeiger - Zeiger -2 q r s q r s a[] a[1] a[2] a[3] a[4] a[5] a[] a[1] a[2] a[3] a[4] a[5] 14
Zeiger-Arithmetik o Zeiger {+, - ganze Zahl o Zeiger {==,!=, <, >, <=, >= Zeiger o Zeiger - Zeiger -1 r = 2 q = 1 q r r q = 3 Die Wahrheit über den Subskrit-Oerator o arbeitet eigentlich auf Zeigern: a [ exr ] ist eine Abkürzung für * (a + exr ) Besiel: exr hat Wert 2 a[] a[1] a[2] a[3] a[4] a[5] a[] a[1] a[2] a[3] a[4] a[5] bool* = ; // ointer to first element bool* end = + 1; // ointer after last element wird noch genau erklärt! bool* = ; // ointer to first element bool* end = + 1; // ointer after last element Feld-nach-Zeiger-Konversion 15
bool* = ; // ointer to first element bool* end = + 1; // ointer after last element Feld-nach-Zeiger-Konversion end Zeiger + ganze Zahl bool* = ; // ointer to first element bool* end = + 1; // ointer after last element Zeiger-Initialisierung end bool* = ; // ointer to first element bool* end = + 1; // ointer after last element bool* = ; // ointer to first element bool* end = + 1; // ointer after last element Zeiger-Vergleich end Dereferenzierung, Zuweisung end false 16
bool* = ; // ointer to first element bool* end = + 1; // ointer after last element bool* = ; // ointer to first element bool* end = + 1; // ointer after last element Zeigerinkrement end Zeiger-Vergleich end false false bool* = ; // ointer to first element bool* end = + 1; // ointer after last element bool* = ; // ointer to first element bool* end = + 1; // ointer after last element Dereferenzierung, Zuweisung end Zeigerinkrement, usw... end false false false false 17
bool* = ; // ointer to first element bool* end = + 1; // ointer after last element bool* = ; // ointer to first element bool* end = + 1; // ointer after last element...usw., Zeigerinkrement end Zeiger-Vergleich end false false false false false false false false false false false false false false false false false false false false false false false false bool* = ; // ointer to first element bool* end = + 1; // ointer after last element fertig! end false false false false false false false false false false false false Warum Zeiger? o Die (geringfügig) schnellere Iteration ist nicht der Punkt (Lesbarkeit sricht oft eher für wahlfreien Zugriff) o Grund1: wir brauchen sie für Felder mit variabler Länge (gleich...) o Grund 2: std:: Container-Algorithmen (Sortieren,...) brauchen Iteratoren Zeiger sind die Iteratoren der Felder! 18
Dynamischer Seicher o wie besorgen wir Seicher, der bei Komilierung nicht vorhersehbar ist? o Sieb des Eratosthenes mit Eingabe von n o Allgemein: Feld variabler Länge New-Ausdrücke new T [exr ] new-oerator Ausdruck vom Ty T * (Zeiger) Ty int, Wert n ; exr nicht notwendigerweise konstant o Effekt: neuer Seicher für ein Feld der Länge n mit zugrundeliegendem Ty T wird bereitgestellt; Wert des Ausdrucks ist Adresse des ersten Elements Der Hea o Hautseicherbereich, aus dem das Programm neuen Seicher holen kann. int* i = new int; Sieb des Eratosthenes bisher: statischer Seicher int main() { bool [1]; for (unsigned int i = ; i < 1; ++i) [i] = false; int* a = new int[3]; hea ( dynamischer Seicher ) // comutation and outut... return ; 19
Sieb des Eratosthenes neu: dynamischer Seicher Sieb des Eratosthenes neu: dynamischer Seicher int main() int main() int main() int main() { { // inut std::cout << "Comute rime numbers in {2,...,n-1 for n =? "; { { // inut std::cout << "Comute rime numbers in {2,...,n-1 for n =? "; unsigned int n; std::cin >> n; unsigned int n; std::cin >> n; bool [1]; for (unsigned int i = ; i < 1; ++i) bool* = new bool[n]; for (unsigned int i = ; i < n; ++i) bool [1]; for (unsigned int i = ; i < 1; ++i) bool* = new bool[n]; for (unsigned int i = ; i < n; ++i) [i] = false; [i] = false; [i] = false; [i] = false; // comutation and outut... // comutation and outut... // comutation and outut... // comutation and outut... delete[] ; delete[] ; return ; Subskrit-Oerator auf Zeiger return ; return ; Freigabe des nicht mehr benötigten dynamischen Seichers return ; Delete-Ausdrücke Mit new erzeugte Objekte haben dynamische Seicherdauer: sie leben, bis sie exlizit gelöscht werden: delete exr int* i = new int;... delete i; Delete-Ausdrücke Mit new erzeugte Objekte haben dynamische Seicherdauer: sie leben, bis sie exlizit gelöscht werden. delete[] exr int* a = new int[3];... delete[] a; delete-oerator Zeiger vom Ty T *, der auf ein vorher mit new bereitgestelltes Objekt zeigt; Effekt: Seicher auf dem Hea wird wieder freigegeben. delete-oerator Zeiger vom Ty T *, der auf ein vorher mit new bereitgestelltes Feld zeigt; Effekt: Seicher auf dem Hea wird wieder freigegeben. 2
Dynamische-Seicher- Richtlinie Zu jedem new gibt es ein assendes delete int* a = new int[3]; Dynamische-Seicher- Richtlinie Zu jedem new gibt es ein assendes delete int* a = new int[3];... delete[] a; Dynamische-Seicher- Richtlinie Zu jedem new gibt es ein assendes delete Nichtbefolgung führt zu Seicherlecks (ungenutzter, aber nicht mehr verfügbarer Seicher auf dem Hea) 21