Klassen-Templates
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy. Lektion 65 von 104.
Genauso wie Funktions-Templates es Ihnen ermöglichen, typunabhängige Funktionen zu schreiben, erlauben es Ihnen Klassentemplates, typunabhängige Klassen zu erstellen. So funktionieren Container wie std::vector – Sie definieren die Struktur einmal, und der Compiler generiert spezifische Versionen für jeden Typ, den Sie verwenden.
template <typename T>
class Box {
T value;
public:
Box(T v) : value(v) {}
T getValue() const { return value; }
void setValue(T v) { value = v; }
};
int main() {
Box<int> intBox(42);
Box<std::string> strBox("Hello");
std::cout << intBox.getValue() << std::endl; // 42
std::cout << strBox.getValue() << std::endl; // Hello
}Im Gegensatz zu Funktions-Templates müssen Sie den Typ explizit angeben, wenn Sie Objekte aus Klassen-Templates unter Verwendung von spitzen Klammern erstellen. Der Compiler generiert dann eine vollständige Klassendefinition für diesen spezifischen Typ.
Klassentemplates können mehrere Typparameter und sogar Nicht-Typparameter wie Ganzzahlen haben:
template <typename T, int Size>
class FixedArray {
T data[Size];
public:
T& operator[](int index) { return data[index]; }
int size() const { return Size; }
};
FixedArray<double, 5> arr; // Array aus 5 Doubles
arr[0] = 3.14;Wenn Sie Elementfunktionen außerhalb der Klasse definieren, müssen Sie die Template-Deklaration wiederholen:
template <typename T>
class Container {
T* data;
public:
Container();
~Container();
};
template <typename T>
Container<T>::Container() : data(nullptr) {}
template <typename T>
Container<T>::~Container() { delete data; }Klassentemplates bilden die Grundlage der generischen Programmierung in C++ und ermöglichen es Ihnen, wiederverwendbare Datenstrukturen zu schreiben, die mit jedem Typ funktionieren.
Aufgabe
EinfachErstellen wir ein generisches Speichersystem mithilfe von Klassenvorlagen, um einen flexiblen Container zu erstellen, der jeden Datentyp aufnehmen kann. Du organisierst deine Vorlagenklasse in einer Header-Datei und demonstrierst ihre Vielseitigkeit mit verschiedenen Typen in deinem Hauptprogramm.
Du erstellst zwei Dateien:
Storage.h: Definiere eine Klassenvorlage namensStorage, die als einfacher Container für einen einzelnen Wert mit einigen nützlichen Operationen dient:Deine
Storage-Vorlage sollte Folgendes enthalten:- Ein privates Element zum Speichern des Werts
- Einen Konstruktor, der einen Anfangswert übernimmt
- Eine
getValue()-Methode, die den gespeicherten Wert zurückgibt - Eine
setValue()-Methode, die den gespeicherten Wert aktualisiert - Eine
isEmpty()-Methode, dietruezurückgibt, wenn der Wert dem standardmäßig konstruierten Wert des Typs T entspricht, andernfallsfalse
Erstelle außerdem eine Klassenvorlage namens
Pairmit zwei Typparametern, die zwei zusammengehörige Werte speichert:- Private Elemente für den ersten und zweiten Wert (potenziell unterschiedlicher Typen)
- Einen Konstruktor, der beide Werte initialisiert
- Methoden
getFirst()undgetSecond(), um die jeweiligen Werte abzurufen - Eine
display()-Methode, die Folgendes ausgibt:(<first>, <second>)
main.cpp: Lies vier Eingaben ein (jeweils in einer eigenen Zeile):- Einen ganzzahligen Wert
- Einen neuen ganzzahligen Wert
- Einen Zeichenkettenwert
- Einen Double-Wert
Demonstriere deine Vorlagen wie folgt:
- Erstelle ein
Storage<int>mit der ersten Ganzzahl und gib Folgendes aus:Int storage: <value> - Aktualisiere es mit der zweiten Ganzzahl mithilfe von
setValue()und gib anschließend Folgendes aus:Updated: <value> - Erstelle ein
Storage<std::string>mit der Zeichenketteneingabe und gib Folgendes aus:String storage: <value> - Prüfe, ob der Zeichenkettenspeicher leer ist, und gib Folgendes aus:
Is empty: <true/false>(gibtrueoderfalseaus) - Erstelle ein
Pair<std::string, double>mit dem Zeichenketten- und dem Double-Wert und rufe anschließenddisplay()auf - Erstelle ein
Pair<int, int>mit beiden ganzzahligen Eingaben und rufe anschließenddisplay()auf
Zum Beispiel mit den Eingaben 42, 100, Hello und 3.14:
Int storage: 42
Updated: 100
String storage: Hello
Is empty: false
(Hello, 3.14)
(42, 100)Beachte, wie dieselbe Storage-Vorlage nahtlos mit Ganzzahlen und Zeichenketten funktioniert und wie Pair unterschiedliche Typen miteinander kombinieren kann. Der Compiler erzeugt für jede von dir verwendete Typkombination separate Klassendefinitionen. Denke daran, für Vorlagen mit einem einzelnen Typ die Syntax template <typename T> und für Vorlagen mit mehreren Typparametern template <typename T, typename U> zu verwenden.
Probier es selbst
#include <iostream>
#include <string>
#include "Storage.h"
using namespace std;
int main() {
// Eingaben lesen
int intValue1;
int intValue2;
string strValue;
double doubleValue;
cin >> intValue1;
cin >> intValue2;
cin >> strValue;
cin >> doubleValue;
// TODO: Erstelle Storage<int> mit der ersten Ganzzahl und gib aus: Int storage: <value>
// TODO: Aktualisiere mit der zweiten Ganzzahl unter Verwendung von setValue(), gib aus: Updated: <value>
// TODO: Erstelle Storage<std::string> mit der String-Eingabe, gib aus: String storage: <value>
// TODO: Überprüfe, ob der String-Speicher leer ist, gib aus: Is empty: true/false
// TODO: Erstelle Pair<std::string, double> mit String und Double, rufe display() auf
// TODO: Erstelle Pair<int, int> mit beiden Ganzzahlen, rufe display() auf
return 0;
}Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienC++ Build & KompilierungHeader-Dateien & QuelldateienNamespaces & GültigkeitsbereichEinführung in OOP mit C++Klassen vs. ObjekteDer „this“-ZeigerMethoden (Member-Funktionen)Attribute (Datenmember)Grundlagen zu Ctors & DtorsRückblick – Einfacher Taschenrechner4Klassenattribute
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen und -KlassenZusammenfassung – Bankkontoverwaltung7Vererbung
Grundlagen der VererbungZugriffsebenen bei der VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick – Mitarbeiterhierarchie2Speicherverwaltung
Stack- vs. Heap-SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Intelligente Zeiger in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsbezeichner in C++Zugriffsbezeichner im DetailInformationsverbergungStruct vs. KlasseVerschachtelte und innere KlassenWiederholung – Studierendenverwaltungssystem8Polymorphie
Polymorphie zur Compilezeit vs. LaufzeitFunktionsüberladungVirtuelle Funktionen erneut betrachtetRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIRückblick – Shape-Calculator3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorMove-KonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktor im DetailDrei-/Fünf-/Null-RegelZusammenfassung – String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladung der Operatoren [] und ()TypkonvertierungsoperatorenWiederholung – Matrix-Klasse9Templates
FunktionstemplatesKlassen-TemplatesTemplate-SpezialisierungVariadische TemplatesGrundlagen zu SFINAE und Type TraitsRückblick – Generischer ContainerÜbe selbstständig: Online-C++-Compiler