Strukturen und Klassen Flashcards
Was machen Structs?
Sie gruppieren Variablen bzw. Werte mit gleicher “Struktur”, sprich sie erstellen einen neuen Typ.
Was ist der formale Wertebereich eines Structs?
Der formale Wertebereich eines Structs setzt sich aus dem kartesischen Produkt der Wertebereiche der bereits existierenden Typen zusammen, die für die Erstellung des Structs verwendet wurden.
Will man also z.B. einen neuen Typ für rationale Zahlen mit einem Struct generieren, so ist der Wertebereich des neuen Typs 𝚛𝚊𝚝 gegeben durch 𝚒𝚗𝚝 × 𝚒𝚗𝚝, da zur Erstellung von rationalen Zahlen x/y zwei ganze Zahlen x und y gebraucht werden.
Was sind Member-Variablen eines Structs?
Die Member-Variablen sind “Untertypen” eines Structs, sprich in einem Struct definierte Variablen mit einem Typ. Wollen wir z.B. eine neues Struct 𝚖𝚘𝚟𝚒𝚎 erstellen…
𝚜𝚝𝚛𝚞𝚌𝚝 𝚖𝚘𝚟𝚒𝚎 { 𝚌𝚑𝚊𝚛 𝚝𝚒𝚝𝚕𝚎; 𝚌𝚑𝚊𝚛 𝚍𝚒𝚛𝚎𝚌𝚝𝚘𝚛; 𝚒𝚗𝚝 𝚛𝚎𝚕𝚎𝚊𝚜𝚎𝚢𝚎𝚊𝚛; }
…, so sind die einzelnen Elemente des Structs, z.B. 𝚌𝚑𝚊𝚛 𝚝𝚒𝚝𝚕𝚎, die Member-Variablen.
Wie kann man auf die einzelnen Member-Variablen eines Structs zugreifen? Wie würde eine Funktion 𝚊𝚍𝚍 für die Addition zweier Werte des Typs 𝚛𝚊𝚝 (rationale Zahlen, in einem Struct definiert) aussehen?
Zuerst definieren wir die rationalen Zahlen durch zwei Werte des Typs 𝚒𝚗𝚝, wobei 𝚗 der Zähler (“Nominator”) und 𝚍 der Nenner (“Denominator”) ist.
𝚜𝚝𝚛𝚞𝚌𝚝 𝚛𝚊𝚝 {
𝚒𝚗𝚝 𝚗;
𝚒𝚗𝚝 𝚍;
}
Um auf die einzelnen Member-Variablen zuzugreifen, brauchen wir den Punkt “.” (ähnlich wie bei Vektoren). Eine Funktion zur Addition rationaler Zahlen würde also wie folgt aussehen:
𝚛𝚊𝚝 𝚊𝚍𝚍 (𝚌𝚘𝚗𝚜𝚝 𝚛𝚊𝚝 𝚊, 𝚌𝚘𝚗𝚜𝚝 𝚛𝚊𝚝 𝚋) { 𝚛𝚊𝚝 𝚛𝚎𝚜𝚞𝚕𝚝; 𝚛𝚎𝚜𝚞𝚕𝚝.𝚗 = 𝚊.𝚗 * 𝚋.𝚍 + 𝚊.𝚍 * 𝚋.𝚗; 𝚛𝚎𝚜𝚞𝚕𝚝.𝚍 = 𝚊.𝚍 * 𝚋.𝚍; 𝚛𝚎𝚝𝚞𝚛𝚗 𝚛𝚎𝚜𝚞𝚕𝚝; }
Wie werden Structs initialisiert?
Wird ein Struct initialisiert, so werden alle Member-Variablen des Structs entsprechend ihrer Typen default-initialisiert. Für Member-Variablen fundamentaler Typen passiert dabei nichts und der Wert bleibt undefiniert.
Sei 𝚜𝚝𝚛𝚞𝚌𝚝 𝚗𝚎𝚠𝚝𝚢𝚙𝚎 ein Struct und
𝚗𝚎𝚠𝚝𝚢𝚙𝚎 𝚜, 𝚝;
die Initialisierung zweier Variablen vom Typ 𝚗𝚎𝚠𝚝𝚢𝚙𝚎. Was passiert bei der Zuweisung 𝚜 = 𝚝; ?
Den Member-Variablen von t werden die Werte der Member-Variablen von s zugewiesen.
Weshalb funktionieren die Vergleichsoperatoren nur für fundamentale Typen, nicht aber für Structs?
Da der Vergleich memberweise passiert und dies für die meisten Structs wenig Sinn machen würde, z.B. bei den rationalen Zahlen mit Struct-Typ 𝚛𝚊𝚝 würde der Vergleich 2/4 = 4/8 als falsch ausgewertet.
Was ist die Signatur einer Funktion und wie ist es dadurch möglich, mehrere Funktion mit gleichem Namen zu definieren?
Eine Funktion ist bestimmt durch Namen, Typen, Anzahl und Reihenfolge der Argumente. Sind zwei Funktionen mit gleichem Namen vorhanden, wählt der Compiler beim Aufruf der Funktion automatisch diejenige, die aufgrund der Signatur “am besten passt”.
Beispiel: Gegeben seien zwei Funktionen: f₁: 𝚒𝚗𝚝 𝚙𝚘𝚠 (𝚒𝚗𝚝 𝚋, 𝚒𝚗𝚝 𝚎) {…} f₂: 𝚒𝚗𝚝 𝚙𝚘𝚠 (𝚒𝚗𝚝 𝚎) {𝚛𝚎𝚝𝚞𝚛𝚗 𝚙𝚘𝚠(𝟸, 𝚎); } Der Compiler entscheidet sich... 𝚜𝚝𝚍::𝚌𝚘𝚞𝚝 << 𝚙𝚘𝚠(𝟹,𝟹); … für f₁; 𝚜𝚝𝚍::𝚌𝚘𝚞𝚝 << 𝚙𝚘𝚠(𝟹); … für f₂.
Was sind Operatoren und was bedeutet es, einen Operator zu überladen?
Operatoren sind spezielle (fundamentale) Funktionen wie +, -, *, /, +=, usw. .
Einen Operator zu überladen, heisst, ihn anders zu definieren, als er ursprünglich definiert worden ist.
Wir können z.B. den Operator “+” durch ein “/” überladen, so dass gilt: 4 + 2 = 2.
Wann ist es sinnvoll, Operatoren zu überladen?
Wenn sie für ein Struct nicht bzw. anders definiert sind, als sie eigentlich müssten.
(z.B. beim Struct der rationalen Zahlen braucht es Änderungen an den Operatoren, da ansonsten die Operationen nicht funktionieren.)
Betrachte folgende Überladung des Operators “+=” im Struct für rationale Zahlen:
𝚛𝚊𝚝 𝚘𝚙𝚎𝚛𝚊𝚝𝚘𝚛+= (𝚛𝚊𝚝 𝚊, 𝚛𝚊𝚝 𝚋) { 𝚊.𝚗 = 𝚊.𝚗 * 𝚋.𝚍 + 𝚊.𝚍 * 𝚋.𝚗; 𝚊.𝚍 *= 𝚋.𝚍; 𝚛𝚎𝚝𝚞𝚛𝚗 𝚊; }
Weshalb funktioniert dieser Code nicht und wie könnte man ihn verbessern?
Der Ausdruck 𝚛 += 𝚜 hat zwar den gewünschten Wert, weil die Aufrufargumente R-Werte sind, jedoch nicht den gewünschten Effekt der Veränderung von 𝚛. Zudem stellt 𝚛 += 𝚜 entgegen der eigentlichen Konvention von C++ keinen L-Wert dar.
Man muss also das auszugebende Resultat als L-Wert deklarieren, sprich: 𝚛𝚊𝚝& 𝚘𝚙𝚎𝚛𝚊𝚝𝚘𝚛+= (𝚛𝚊𝚝& 𝚊, 𝚛𝚊𝚝 𝚋) { 𝚊.𝚗 = 𝚊.𝚗 * 𝚋.𝚍 + 𝚊.𝚍 * 𝚋.𝚗; 𝚊.𝚍 *= 𝚋.𝚍; 𝚛𝚎𝚝𝚞𝚛𝚗 𝚊; }
Welche Operatoren kann man neben arithmetischen Operatoren auch noch überladen? Wozu ist dies nützlich?
Man kann ebenso Ein- und Ausgabeoperatoren überladen, falls man eine konkretere Ausgabe erhalten möchte bzw. von einer allgemeineren Eingabe lesen möchte.
Beispiel:
Im Fall des Structs der rationalen Zahlen sei die Eingabe definiert als:
𝚜𝚝𝚍::𝚒𝚜𝚝𝚛𝚎𝚊𝚖& 𝚘𝚙𝚎𝚛𝚊𝚝𝚘𝚛» (𝚜𝚝𝚍::𝚒𝚜𝚝𝚛𝚎𝚊𝚖& 𝚒𝚗, 𝚛𝚊𝚝& 𝚛) {
𝚌𝚑𝚊𝚛 𝚌;
𝚛𝚎𝚝𝚞𝚛𝚗 𝚒𝚗»_space; 𝚛.𝚗»_space; 𝚌»_space; 𝚛.𝚍;
}
Hierbei ist c der separierende Charakter ‘/’. Das Programm liest also ein 𝚛 aus dem Eingabestrom und gibt diesen als L-Wert zurück.
Betrachten wir ein Struct für rationale Zahlen 𝚛𝚊𝚝.
Wie würden wir vorgehen, wenn wir dies in eine Bibliothek bringen wollen?
Die Definition des Structs sowie die Deklarationen aller Funktionen kommt in die Header-Datei 𝚛𝚊𝚝.𝚑,
die arithmetischen Operatoren, die relationalen Operatoren sowie die Ein-/Ausgabe-Operatoren gehören in die Programmdatei 𝚛𝚊𝚝.𝚌𝚙𝚙.
Was sind Klassen und wozu braucht man sie?
Klassen sind Varianten von Structs, die eine sog. Datenkapselung in C++ ermöglichen. Bei Structs werden den Member-Variablen öffentlicher Zugriff gewährt, wobei sie bei Klassen jedoch privat sind, d.h. nicht sichtbar. Man kann in Klassen jedoch weitere Unterteilungen in 𝚙𝚞𝚋𝚕𝚒𝚌 und 𝚙𝚛𝚒𝚟𝚊𝚝𝚎 machen, um so gewisse Teile noch öffentlich sichtbar zu lassen.
Somit ist es eigentlich nur das Default-Setting, das bei Klassen (privat) und Strukturen (public) anders ist.
Was ist eine Memberfunktion?
Eine Memberfunktion ist eine Funktion, die auf Membervariablen zugreift und diese ändert.
Beispiel:
Sei eine Klasse 𝚊𝚞𝚝𝚘 gegeben:
𝚌𝚕𝚊𝚜𝚜 𝚊𝚞𝚝𝚘 {
𝚞𝚗𝚜𝚒𝚐𝚗𝚎𝚍 𝚒𝚗𝚝 𝙿𝚛𝚎𝚒𝚜;
𝚜𝚝𝚍::𝚜𝚝𝚛𝚒𝚗𝚐 𝙼𝚊𝚛𝚔𝚎;
𝚜𝚝𝚍::𝚜𝚝𝚛𝚒𝚗𝚐 𝙱𝚎𝚜𝚒𝚝𝚣𝚎𝚛;
𝚟𝚘𝚒𝚍 𝚅𝚎𝚛𝚔𝚊𝚞𝚏(𝚜𝚝𝚍::𝚜𝚝𝚛𝚒𝚗𝚐 𝙱𝚎𝚜𝚒𝚝𝚣𝚎𝚛_𝚗𝚎𝚞) {
𝙱𝚎𝚜𝚒𝚝𝚣𝚎𝚛 = 𝙱𝚎𝚜𝚒𝚝𝚣𝚎𝚛_𝚗𝚎𝚞;
}
}
Die Funktion 𝚅𝚎𝚛𝚔𝚊𝚞𝚏 ändert den Besitzer des Autos, greift also auf eine Membervariable zu und ändert diese.