Einleitung
pair Datentypen
make_pair Hilfsfunktion
Zugriff auf pair-Elemente
pair als Returnwert
pair-Operatoren
Beispiel und Übung
Der in der Standard Bibliothek definierte Datentyp pair dient zum Zusammenfassen von zwei Daten, deren Datentypen beliebig sein können. Mithilfe von pair können Sie damit sogar zwei Werte aus einer Funktion/Memberfunktion zurückgeben, indem Sie als Returntyp der Funktion/Memberfunktion pair verwenden. Dazu folgt später noch ein Beispiel.
Wenn Sie den Datentyp pair einsetzen, müssen Sie die Header-Datei <utility> einbinden. Außerdem liegt der Datentyp pair, wie übrigens alle Standard Bibliothek Komponenten, im Namensraum std. Beachten Sie dies bitte beim Einsatz der Komponenten! Wenn Sie dies nicht tun, erhalten eine Fehlermeldung, dass die entsprechende Komponente nicht bekannt ist.
Allen in einem pair abgelegten Elementen ist eines gemeinsam: sie sind immer eine Kopie des ursprünglichen Elements. Werden also nach dem Ablegen eines Elements in einem pair noch Änderungen an einem der Elemente vorgenommen, so wirken sich diese nicht auf das im pair abgelegte Element aus.
Um ein Objekt des Datentyps pair zu definieren, geben Sie nach dem Datentyp pair in spitzen Klammern die beiden Datentypen an, die im pair Objekt abgelegt werden sollen. Im Anschluss daran folgt dann wie üblich der Objektname. Geben Sie nach dem Objektnamen nichts weiter an, so werden die beiden Elemente (bei einfachen Datentypen wie char, int usw.) mit 0 initialisiert.
// pair für einen int und einen long std::pair<int, long> emptyPair; // pair für char* und float std::pair<char*, float> cfPair("any text", 1.2f); // Kopieren eines pair Objekts std::pair<char*, float> copyPair(cfPair); |
Zusätzlich stellt pair noch Konstruktore bereit, um ein pair Objekt bei seiner Definition mit Werten zu initialisieren (zweite Anweisung) oder eine Kopie eines bestehenden pair Objekts zu erstellen (dritte Anweisung). Sie müssen bei der Initialisierung eines pair Objekts natürlich auf die Datentypen achten, mit denen Sie das pair Objekt initialisieren.
Außer einfache Datentypen können in einem pair Objekt auch Objekte abgespeichert werden. Diese Objekte müssen aber die folgenden Eigenschaften besitzen:
// Klasse die die pair Anforderung erfüllt class Any { .... public: // Standard-ctor für Initialisierung mit 'leerem' pair Any(); // copy-ctor Any(const Any& CObj); // Überladener Zuweisungsoperator Any& operator = (const Any& COp2); // Überladener < Operator friend bool operator < (const Any& COp1, const Any& COp2); // Uberladener == Operator friend bool operator == (const Any& COp1, const Any& COp2); .... }; |
Oben sehen Sie eine Klasse, die die genannten Anforderungen erfüllt. Beachten Sie bitte, dass die Vergleichsoperatoren < und == durch eine friend-Funktion überladen sind. Wie der Vergleich von pair Daten letztendlich durchgeführt wird, das sehen wir uns nachher gleich an.
Um dann ein Objekt in einem pair abzulegen, geben Sie innerhalb der spitzen Klammer die Klasse des Objekts an. Die im pair abgelegten Daten können dabei beliebig aus Objekten, Objektzeigern, Standard-Datentypen und Zeigern zusammengesetzt sein. Ja ist sogar möglich, als Datentyp ein weiteres pair zu verwenden.
// Leeres pair mit Any-Objekt und float std::pair<Any, float> emptyPair; // pair mit zwei Window-Objekten std::pair<Window, Window> winPair(Window(...), Window(...)); // pair mit string und Window-Zeiger std::pair<string, Window*> swPair("Mein Fenster", new Window(...)); |
Sehen wir uns jetzt aber einmal genauer an was vonstatten geht, wenn Sie ein pair definieren in dem Objekte abgelegt werden.
// 1. Fall: leeres pair mit anschl. Zuweisung
std::pair<Any, float> aPair;
aPair = std::pair<Any,float>(Any(...), 1.2f);
// 2. Fall: Objekt getrennt erstellen und dann im pair aufnehmen
Any myObj(...);
std::pair<Any, float> bPair(myObj, 2.0f);
// 3. Fall: Objekt bei pair Definition erzeugen
std::pair<Any, float> cPair(Any(...),3.0f);
// 4. Fall: pair Kopie anlegen
std::pair<Any, float> dPair(initPair);
|
1. Fall
Bei der Definition eines leeren pair Objekts wird zuerst der Standard-Konstruktor des im pair abgelegten Objekts ausgeführt, um das Objekt zu initialisieren. Bei einer anschließenden Zuweisung an das pair-Objekt wird dann zunächst ein temporäres Objekt von der im pair angegebenen Klasse erzeugt. Dieses temporäre Objekt wird per Kopierkonstruktor in ein neues, weiteres pair Objekt übertragen (das pair-Objekt, welches rechts vom Zuweisungsoperator steht). Erst dann wird dieses neue pair per Zuweisung dem bisherigem leeren pair zugewiesen, was wiederum zum Aufruf des Zuweisungsoperator des im pair abgelegten Objekts führt. Und zum Schluss wird das Objekt im pair rechts vom Zuweisungsoperator so wie das temporäre Objekt gelöscht. Sie sehen, dieses Verfahren ist relativ aufwändig.
2. Fall
Es wird zuerst das im pair abzulegende Objekt erstellt und dieses dann einem pair bei seiner Definition übergeben. Bei der pair Definition wird der Kopierkonstruktor des im pair abgelegten Objekts ausgeführt, um eine Kopie des ursprünglichen Objekts im pair abzulegen.
3. Fall
Bei der Definition eines pair wird auch gleichzeitig das darin abzulegende Objekt definiert. In diesem Fall wird zuerst ein temporäres Objekt erzeugt , das dann per Kopierkonstruktor in das pair übernommen wird. Nach der Übernahme wird dieses temporäre Objekt wieder gelöscht.
4. Fall
Wird ein pair bei seiner Definition mit einem anderen pair initialisiert, so wird der Kopierkonstruktor des Objekts aufgerufen, das im ursprünglichen pair abgelegt ist.
Die in einem pair abgelegten Objekte werden beim Löschen des pair ebenfalls gelöscht, d.h. es wird der Destruktor des im pair abgelegten Objekts automatisch aufgerufen.
Obacht geben müssen Sie, wenn Sie statt Daten oder Objekte Zeiger in einem pair ablegen. Zum einen wird, wenn Objektzeiger im pair abgelegt sind, beim Löschen des pair nicht mehr automatisch das über den Zeiger referenzierte Objekt gelöscht, sondern nur der Objektzeiger. Sie müssen das Objekt also explizit selbst (vorher!) entfernen.
Und zum anderen wird beim Duplizieren eines solchen pair nur der Zeiger dupliziert. Damit verweisen die Zeiger in beiden pair auf die gleiche Speicherstelle bzw. auf das gleiche Objekt! Das trifft auch dann zu, wenn Sie zwei solche pair einander zuweisen; auch hier werden nur die Zeiger zugewiesen. Seien Sie also vorsichtig mit pair die Zeiger enthalten!
// pair mit Objektzeiger erzeugen std::pair<Any*, float> pPair(new Any(...), 4.0f); ... // pair duplizieren, dupliziert Objektzeiger! std::pair<Any*, float> cPair(pPair); |
Von pair Objekten lassen sich auch entsprechende Felder bilden. Mithilfe des gleich beschriebenen Funktions-Templates make_pair(...) lässt sich solch ein Feld auch recht einfach initialisieren.
std::pair<std::string, float> myPairs[] = {
std::pair<std::string,float>("Toast",1.60f),
std::pair<std::string,float>("Butter",1.05f)
};
|
Die Standard-Bibliothek enthält das Funktions-Template make_pair(...) das es erlaubt, ein pair ohne die explizite Angabe der pair-Datentypen zu erstellen. Dazu erhält make_pair(...) lediglich die beiden Daten/Objekte als Parameter übergeben, die im pair-Objekt abgelegt werden sollen.
// Definition des pair Objekts std::pair<char*, float> myPair; // Zuweisung ohne make_pair(...) myPair = std::pair<char*,float> ("Toast",1.60f); // Zuweisung mit make_pair myPair = std::make_pair("Toast",1.60f); |
Im Beispiel wird zunächst ein leeres pair Objekt definiert. Diesem pair Objekt wird dann einmal normal ein 'Wert' zugewiesen und einmal mithilfe des make_pair(...) Funktions-Templates.
|
std::pair<std::string, int> myPair; Richtig geht's so: std::pair<std::string, int> myPair; |
Da pair in der Standard-Bibliothek als struct-Datentyp realisiert ist (alle Elemente sind public!), kann direkt auf die beiden im pair abgelegten Werte zugegriffen werden. Das erste Element ist über den Elementnamen first erreichbar und das zwei Element über den Name second.
// pair Definition std::pair<std::string, float> myPair("Toast",1.60f); // Ausgabe der beiden pair-Elemente std::cout << myPair.first << ',' << myPair.second << std::endl; |
Der Datentyp pair lässt sich auch sehr gut als Returntyp von Funktionen/Memberfunktionen einsetzen, die mehr als einen Wert zurückliefern. Sehen Sie sich dazu einmal das nachfolgende Beispiel an.
// Funktion zur Berechnung einer Wurzel // Liefert ein pair zurück inline std::pair<bool,double> CalcSqrt(double val) { if (val < 0) return make_pair(false,0.0f); else return make_pair(true,sqrt(val)); } ... // pair für Returnwert std::pair<bool,double> result; ... // Wurzel berechnen lassen result = CalcSqrt(-1.44); // Ergebnis abprüfen if (!result.first) std::cout << "CalcSqrt() Fehler!"; else std::cout << "Wurzel: " << result.second; |
Die Funktion CalcSqrt(...) dient zur Berechnung der Quadratwurzel aus einer Zahl. Als Returnwert liefert sie ein pair, in dessen ersten Element first der Erfolg oder Misserfolg der Wurzelberechnung in Form eines bool-Werts abgelegt ist. Konnte die Wurzel erfolgreich berechnet werden, so enthält das zweite Element die Wurzel aus dem übergebenen Wert.
Beachten Sie auch, dass die Funktion CalcSqrt(...) als inline-Funktion definiert ist damit der Aufruf der Funktion keinen unnötigen Overhead erzeugt. Außerdem wird zur Erzeugung des pair Rückgabewerts die Funktion make_pair(...) verwendet.
Der Datentyp pair überlädt unter anderem auch den Zuweisungsoperator. Der Zuweisungsoperator weist die beiden Elemente first und second einander direkt zu. Wenn Sie Objekte in einem pair ablegen, so sollte die Klasse des Objekts den Zuweisungsoperator also ebenfalls überladen.
class Any
{
....
Any& operator = (const Any& rhs)
{
... // Zuweisungen der Any-Elemente
}
};
...
std::pair<int,Any> firstPair(1,Any(...));
std::pair<int,Any> secondPair;
...
// Ruft = Operator von Any auf
secondPair = firstPair;
|
Außer dem Zuweisungsoperator überlädt pair noch die Vergleichsoperatoren < und ==. Die restlichen Vergleichsoperatoren, wie z.B. >= und !=, werden, wie bereits erwähnt, aus diesen beiden überladenen Operatoren gebildet.
Wenn ein pair Objekte enthält und Sie die pairs
miteinander vergleichen wollen, so muss die Klasse der Objekte die
beiden Operatoren < und == durch friend-Funktionen
überladen.
class Any
{
....
friend bool operator < (const Any& lhs, const Any& rhs);
friend bool operator == (const Any& lhs, const Any& rhs);
};
...
std::pair<int,Any> firstPair(1,Any(...));
std::pair<int,Any> secondPair(2,Any(...));
std::pair<Any,int> myPair(Any(...),3);
std::pair<Any,int> yourPair(Any(...),1);
...
// Vergleicht zuerst int und dann Any
if (firstPair < secondPair)
....
// Vergleicht zuerst Any und dann int
if (myPair != yourPair)
...
// Direkter Vergleich der Elemente immer möglich
if (myPair.first == yourPair.first)
...
|
Beim Vergleichen von pair Objekten hat das Element first Vorrang. D.h. nur wenn der Vergleich von first true ist, wird das Element second mit in der Vergleich einbezogen. Achten Sie deshalb bei der Definition eines pair Objekts darauf, dass die pair Elemente in der richtigen Reihenfolge definiert sind. Wollen Sie zum Beispiel ein pair Objekt für eine Gehaltsliste (bestehend aus einem string für den Namen und einen float für das Gehalt) nach Namen vergleichen, so muss der Name als erstes angegeben werden. Wenn Sie dagegen die Gehälter vergleichen wollen, so sollten Sie das Gehalt als erstes angegeben. Die Reihenfolge ist aber nur dann relevant, wenn Sie die pair Objekte direkt miteinander vergleichen. Es bleibt Ihnen aber immer die Option offen, die Elemente first bzw. second eines pair direkt miteinander zu vergleichen.
|
|
|
|
Pair-Array erstellen: |
// Beispiel zu pair #include <iostream> #include <iomanip> #include <utility> #include <string> using std::cout; using std::endl; using std::string; // Minimale Demo Klasse für pair Anwendung // ctor und dtor enthalten Ausgaben class Demo { string text; // Nutzdatum public: // ctors Demo() { cout << "std-ctor" << endl; } Demo(const char* pText): text(pText) { cout << "param-ctor" << endl; } Demo(const Demo& obj): text(obj.text) { cout << "copy-ctor" << endl; } // dtor ~Demo() { cout << "dtor" << endl; } // Zuweisungsoperator Demo& operator = (const Demo& lhs) { if (this == &lhs) // Zuweisung auf sich selbst abfangen! return *this; text = lhs.text; cout << "= operator" << endl; return *this; } // Überladene Operatoren friend bool operator < (const Demo& rhs, const Demo& lhs); friend bool operator == (const Demo& rhs, const Demo& lhs); friend std::ostream& operator << (std::ostream& os, const Demo& obj); }; // Kleiner als Operator bool operator < (const Demo& lhs, const Demo& rhs) { return lhs.text < rhs.text; // string überlädt < Operator } // Gleich Operator bool operator == (const Demo& rhs, const Demo& lhs) { return rhs.text == lhs.text; // string überlädt == Operator } // Shift left Operator für Ausgabe std::ostream& operator << (std::ostream& os, const Demo& obj) { cout << obj.text; return os; } // main() Funktion int main() { // pair-Array mit double/Demo-Objekten initialisieren cout << "Pair-Array erstellen:\n"; std::pair<double,Demo> income[] = { std::make_pair(5000.00,Demo("Gustav Gans")), std::make_pair(3000.00,Demo("Daisy Duck")), std::make_pair(100000.00,Demo("Dagobert Duck"))}; // Anzahl der Einträge im pair-Array const size_t SIZE = sizeof(income)/sizeof(income[0]); // Ausgabe auf 2 Dezimalstellen cout << std::fixed<< std::setprecision(2); // pair-Array ausgeben for (size_t index=0; index<SIZE; index++) { cout << "Einkommen: " << income[index].first << " Goldtaler"; cout << "\t\tName: " << income[index].second << endl; } // Vergleich zweier Einkommen cout << income[0].second << " verdient "; cout << ((income[0] < income[1])? "weniger" : "mehr"); cout << " als " << income[1].second << endl; } |
Schreiben Sie ein Programm zum Abspeichern von Aktienkursen, wobei jeder Kurs aus einem Namen (als string-Datentyp) und dem Aktienwert (als float-Wert) besteht. Die Aktienkurse sind in einem entsprechenden pair-Feld abzulegen. Initialisieren das pair-Feld mit folgenden Werten:
| Daimer
333.33f VW 111.11f BMW 222.22f General Motors 11.11f |
Das pair-Feld ist so anzulegen, dass die Aktienkurse auf einfache Weise verglichen werden können.
Geben Sie alle Aktienkurs aus.
Erhöhen Sie danach den Kurs der VW-Aktie um 111.11 und sortieren Sie die Aktienkurse aufsteigend nach ihrem Kurswert. Geben Sie zur Kontrolle nochmals alle Aktienkurse aus.
|
Aktienkurse: Kurse nach Wert/Hersteller sortiert: |