Funktionstemplates
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy. Lektion 64 von 104.
Stell dir vor, du schreibst eine Funktion, um das Maximum zweier Ganzzahlen zu finden, und stellst dann fest, dass du dieselbe Logik erneut für Gleitkommazahlen und nochmals für Zeichenketten benötigst. Ohne Vorlagen müsstest du für jeden Typ nahezu identische Funktionen schreiben. Funktionsvorlagen lösen dieses Problem, indem sie dir ermöglichen, die Logik einmal zu schreiben und den Compiler automatisch typspezifische Versionen erzeugen zu lassen.
Eine Funktionsvorlage verwendet das Schlüsselwort template, gefolgt von Vorlagenparametern in spitzen Klammern:
template <typename T>
T maximum(T a, T b) {
return (a > b) ? a : b;
}
int main() {
std::cout << maximum(5, 3) << std::endl; // Verwendet int-Version
std::cout << maximum(3.14, 2.71) << std::endl; // Verwendet double-Version
std::cout << maximum('a', 'z') << std::endl; // Verwendet char-Version
}Der Compiler untersucht jeden Aufruf und generiert eine konkrete Funktion für diesen spezifischen Typ. Dieser Prozess wird als template instantiation bezeichnet. Du kannst den Typ bei Bedarf auch explizit angeben:
std::cout << maximum<double>(5, 3.14) << std::endl; // Erzwingt die double-VersionTemplates können mehrere Typparameter haben und dadurch noch flexiblere Designs ermöglichen:
template <typename T, typename U>
void printPair(T first, U second) {
std::cout << first << ", " << second << std::endl;
}
printPair(42, "hello"); // T=int, U=const char*
printPair(3.14, 100); // T=double, U=intFunktionstemplates bieten Polymorphie zur Kompilierzeit – der Typ wird beim Kompilieren des Codes bestimmt, nicht zur Laufzeit. Das bedeutet keinen Laufzeit-Overhead im Vergleich zu virtuellen Funktionen, wodurch Templates ideal für performancekritischen generischen Code sind.
Aufgabe
EinfachErstellen wir ein Hilfswerkzeug, das Funktionsvorlagen verwendet, um wiederverwendbare Operationen zu erstellen, die mit jedem kompatiblen Typ funktionieren. Du organisierst deine generischen Funktionen in einer Header-Datei und demonstrierst ihre Flexibilität in deinem Hauptprogramm.
Du erstellst zwei Dateien:
MathUtils.h: Definiere eine Sammlung von Funktionsvorlagen, die häufige Operationen ausführen:minimum: eine Vorlagenfunktion, die zwei Werte desselben Typs entgegennimmt und den kleineren zurückgibt.clamp: eine Vorlagenfunktion, die drei Parameter entgegennimmt: einen Wert, eine untere Grenze und eine obere Grenze. Sie gibt den auf die Grenzen beschränkten Wert zurück (die untere Grenze, wenn der Wert kleiner als die untere Grenze ist, die obere Grenze, wenn der Wert größer als die obere Grenze ist, andernfalls den Wert selbst).swapValues: eine Vorlagenfunktion, die zwei Referenzen desselben Typs entgegennimmt und ihre Werte vertauscht.main.cpp: Lies sechs Eingaben ein (jeweils in einer eigenen Zeile):- Erste Ganzzahl
- Zweite Ganzzahl
- Ein double-Wert, der beschränkt werden soll
- Untere Grenze (double)
- Obere Grenze (double)
- Ein Zeichen
Demonstriere deine Vorlagen, indem du:
- Das Minimum der beiden Ganzzahlen bestimmst und Folgendes ausgibst:
Min of <a> and <b>: <result> - Das Minimum des Zeichens
'm'und deines Eingabezeichens bestimmst und Folgendes ausgibst:Min of m and <char>: <result> - Den double-Wert beschränkst und Folgendes ausgibst:
Clamp <value> to [<low>, <high>]: <result> - Die erste Ganzzahl auf den Bereich [0, 100] beschränkst und Folgendes ausgibst:
Clamp <value> to [0, 100]: <result> - Die beiden Ganzzahlen vertauschst und Folgendes ausgibst:
After swapValues: <a>, <b>
Zum Beispiel bei den Eingaben 25, 10, 3.7, 1.0, 5.0 und z:
Min of 25 and 10: 10
Min of m and z: m
Clamp 3.7 to [1, 5]: 3.7
Clamp 25 to [0, 100]: 25
After swapValues: 10, 25Beachte, wie jede Vorlagenfunktion nahtlos mit Ganzzahlen, double-Werten und Zeichen funktioniert: Der Compiler erzeugt für jeden von dir verwendeten Typ die passende Version. Deine Funktion swapValues sollte die ursprünglichen Variablen über Referenzen ändern und damit zeigen, dass Vorlagen genauso wie reguläre Funktionen mit Referenzparametern funktionieren.
Probier es selbst
#include <iostream>
#include "MathUtils.h"
using namespace std;
int main() {
// Eingaben lesen
int a, b;
double value, low, high;
char ch;
cin >> a;
cin >> b;
cin >> value;
cin >> low;
cin >> high;
cin >> ch;
// TODO: Verwende das minimum-Template, um das Minimum von zwei Ganzzahlen zu finden
// Ausgeben: "Min of <a> and <b>: <result>"
// TODO: Verwende das minimum-Template, um das Minimum von 'm' und dem Eingabezeichen zu finden
// Ausgeben: "Min of m and <char>: <result>"
// TODO: Verwende das clamp-Template auf den double-Wert
// Ausgeben: "Clamp <value> to [<low>, <high>]: <result>"
// TODO: Verwende das clamp-Template auf die erste Ganzzahl mit dem Bereich [0, 100]
// Ausgeben: "Clamp <value> to [0, 100]: <result>"
// TODO: Verwende das swapValues-Template auf die beiden Ganzzahlen
// Print: "After swapValues: <a>, <b>"
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