Dynamische Datenstrukturen Flashcards
Aufgrund welches Kriteriums ist ein Vektor eine dynamische Datenstruktur?
Da sich die Grösse des Vektors zur Laufzeit ändern kann (durch Operationen wie 𝚙𝚞𝚜𝚑_𝚋𝚊𝚌𝚔), ist ein Vektor eine dynamische Datenstruktur.
Was ist ein Array und was macht der 𝚗𝚎𝚠-Ausdruck?
Mit dem 𝚗𝚎𝚠-Operator kann man einen neuen, zusammenhängenden Bereich im Speicher für n Elemente allozieren (reservieren). Dieser Speicherbereich wird Array genannt.
Was ist 𝚙 im Ausdruck 𝚙 = 𝚗𝚎𝚠 𝚒𝚗𝚝[𝟽]?
Der Wert von 𝚙 ist die Startadresse des (neu angelegten) Speicherblocks des Typs 𝚒𝚗𝚝 mit 7 Elementen.
Was ist der Unterschied zwischen dem 𝚗𝚎𝚠-Ausruck und einem Vektor?
Beim 𝚗𝚎𝚠-Ausdruck lebt der Speicher nicht nur innerhalb des Scopes des Ausdrucks, sondern bis Ende Programm oder bis der mit 𝚗𝚎𝚠 allozierte Speicher explizit freigegeben wird (mit der 𝚍𝚎𝚕𝚎𝚝𝚎-Funktion).
Zum Vergleich lebt beim Vektor der Speicher nur innerhalb des Funktionsaufrufs.
Was ist ein Zeiger und wie sieht ein Zeiger syntaktisch aus?
Ein Zeiger ist ein Ausdruck und wird mit 𝚃* 𝚟𝚗𝚊𝚖𝚎 initialisiert, wobei 𝚃 der dem Pointer zugrunde liegende Typ ist und 𝚟𝚗𝚊𝚖𝚎 die Variable des Zeigers.
Was ist der Adress-Operator und wie ist sieht er syntaktisch aus?
Der Adress-Operator &𝚟𝚗𝚊𝚖𝚎 gibt die Adresse eines bestehenden Ausdrucks 𝚟𝚗𝚊𝚖𝚎 bekannt.
Es ist ein L-Wert, d.h. man kann mit der Adresse des Ausdrucks auch weiterarbeiten.
Sei 𝚒 ein Ausduck des Typs 𝚒𝚗𝚝 mit Wert 𝚒 = 𝟻. Wie würde man syntaktisch einen Pointer 𝚙 mit der Adresse von 𝚒 initialisieren?
𝚒𝚗𝚝* 𝚙 = &𝚒
Ein Zeiger 𝚙 mit Adresse von 𝚒 als Wert
Was macht der Dereferenz-Operator und wie sieht er syntaktisch aus?
Der Dereferenz-Operator * hat als Wert den Wert des Ausdrucks an der Adresse von p. (Achtung: Hier steht der Stern vor dem bereits initialisierten Ausdruck, der Typ muss also nicht angegeben werden!)
Zum Beispiel ist 𝚒𝚗𝚝 𝚒 = 𝟻 und 𝚒𝚗𝚝* 𝚙 = &𝚒 ein Zeiger auf die Adresse von 𝚒, dann ist *𝚙 wiederum der Wert mit Typ von 𝚒, sprich *𝚙 = 𝟻 mit Typ 𝚒𝚗𝚝.
Was ist der Null-Zeiger und wie sieht er syntaktisch aus und was gilt es bei seiner Verwendung zu beachten?
Der Null-Zeiger ist ein spezieller Zeigertyp, der angibt, dass noch auf kein Objekt gezeigt wird. Er wird zur Problemfindung und zur Fehlerbehebung gebraucht, um undefiniertes Verhalten zu vermeiden. Syntaktisch wird er durch 𝚃* 𝚙 = 𝚗𝚞𝚕𝚕𝚙𝚝𝚛 initialisiert.
Achtung: Der Null-Zeiger kann aufgrund der soeben genannten Eigenschaften nicht dereferenziert werden, da ansonsten ein Laufzeitfehler auftritt.
Wie kann man mit Zeigern arithmetische Operationen durchführen und was ist der Nutzen davon?
Da ein neuer Zeiger 𝚃* 𝚙 = 𝚗𝚎𝚠 𝚃[𝚗] immer auf den ersten Block des neuen Speichers zeigt, kann man mit arithmetischen Operationen wie z.B. der Addition auf hintere Blöcke zugreifen. So gibt z.B. *(𝚙 + 𝟹) den Wert des vierten Blocks aus.
(Zählung wie bei Vektoren bei null beginnend.)
Welches sind die drei Zeigeroperatoren (abgesehen der arithmetischen Operatoren) und welche Stelligkeit, Präzedenz und Assoziativität haben sie? Welche Art von Werten werden welcher Art von Werten zugeordnet (L- / R-Wert)?
Die drei Zeigeroperatoren sind: (In Klammern Stelligkeit, Präzendenz, Assoziativität und Zuordnung in dieser Reihenfolge)
- Subskript [𝚗] (2, 17, links, R → L)
- Dereferenzierung * (1, 16, rechts, R → L)
- Adresse & (1, 16, rechts, L → R)
Bemerke, dass der Subskript-Operator eine “direkte Form” des Dereferenzierungs-Operators ist:
*(𝚙 + 𝚒) ⇔ 𝚙[𝚒].
Wie kann man über ein Array iterieren? Beantworte die Frage an einem Beispiel, wie der Codeausschnitt eines Programms aussehen würde, das den Durchschnitt von fünf in einem Array gespeicherten Zahlen ermitteln will?
Man lässt eine Schlaufe über alle Adressen des Arrays gehen und wertet diese mit dem Dereferenzierungs-Operator aus. Für besagtes Programm mit einem Pointer 𝚙 könnte man verwenden:
𝚏𝚘𝚛 (𝚒𝚗𝚝 𝚒 = 0; 𝚒 < 𝟻; 𝚒++) {
𝚜𝚞𝚖 += 𝚙[𝚒];
}
𝚛𝚎𝚝𝚞𝚛𝚗 (𝚜𝚞𝚖 / 𝟻);
Was ist Random Access und sequentieller Zugriff? Weshalb ist sequentieller Zugriff für eine Zeigeriteration besser geeignet als Random Access?
Random Access (dt.: Wahlfreier Zugriff) beschreibt das “Springen” an beliebige Adressen des Arrays, wo hingegen sequentieller Zugriff immer nur zur nächsten bzw. vorherigen Adresse springt.
Syntaktisch:
Random Access → 𝚙[0], 𝚙[𝟷] oder *(𝚙+0), *(𝚙+𝟷), …
Sequentiell → ++𝚙, ++𝚙, ++𝚙, …
Beim Iterieren ist der sequentielle Zugriff schneller, da die immer nur addiert werden muss. Als Metapher nehme man ein Buch, das man liest. Beim sequentiellen Zugriff blättert man einfach immer auf die nächste Seite, wohingegen man beim Random Access das Buch immer wieder zuklappt, bevor man die nächste Seite öffnet.
Was ist der hauptsächliche Unterschied (und auch Nachteil) eines statischen Arrays zu einem dynamischen Array?
Bei einem statischen Array ist ein grosser Nachteil, dass die Grösse des Arrays konstant sein muss, z.B. 𝟻 oder 𝟺*𝟹+𝟸, jedoch kein erst später eingelesener Wert, wie z.B. 𝚜𝚝𝚍::𝚌𝚒𝚗»_space; 𝚗.
Wie würde eine Funktion aussehen, mit der man ein Intervall eines Arrays mit Werten füllt?
𝚟𝚘𝚒𝚍 𝚏𝚒𝚕𝚕(𝚒𝚗𝚝* 𝚋𝚎𝚐𝚒𝚗, 𝚒𝚗𝚝* 𝚎𝚗𝚍, 𝚒𝚗𝚝 𝚟𝚊𝚕𝚞𝚎) {
𝚏𝚘𝚛 (𝚒𝚗𝚝* 𝚙 = 𝚋𝚎𝚐𝚒𝚗; 𝚙 != 𝚎𝚗𝚍; ++𝚙)
*𝚙 = 𝚟𝚊𝚕𝚞𝚎;
}
Man definiert folglich Anfang und Ende des Intervalls und füllt die Blöcke dazwischen mit dem gewünschten Wert.