Verbundobjekte in C++
Fundamentale Objekte
Ein fundamentaler Typ ist einer der fest in der Programmiersprache eingebauten Typen für ganze Zahlen, Gleitkommazahlen und so weiter. Wir geben hier keine vollständige Aufzählung aller fundamentalen Typen an, da es für den Anfang reicht, an die beiden Beispiele »int« und »double« zu denken. (Der Typ »::std::string« und Adreßtypen sind nicht fundamental.)
Ein fundamentales Objekt (oder „Fundamentalobjekt “) ist ein Objekt eines fundamentalen Typs.
Das folgende Programm zeigt ein fundamentales Objekt. Es enthält einen einzelnen Wert eines fundamentalen Typs.
main.cpp
#include <initializer_list>
#include <ostream>
#include <iostream>int main()
{ auto first { 8 }; /* Fundamentalobjekt */
::std::cout << first << '\n'; }transcript
8
Es sei hier daran erinnert, daß ein Objekt ein Speicher ist, der einen Wert eines Typs aufnehmen kann. Das Objekt »first« ist beispielsweise der Name eines int-Objektes, also eines Objektes, welches einen Wert des Typs »int« aufnehmen kann.
Eine Variable ist dann praktisch ein Name für ein bestimmtes Objekt zusammen mit jenem Objekt, wie beispielsweise »first«. Im Unterschied zur Variablen »first«, die den Namen »first« hat, hat das Objekt aber keinen Namen. (Es gibt auch Objekte ohne eine zugehörige Variable.)
Verbundobjekte und ihre Unterobjekte
Verbundobjekte traten wohl erstmals in der Programmiersprache COBOL auf.
Ein Verbundobjekt ist ein Objekt, das eine beliebige (nicht-negative und ganzzahlige) Anzahl (0, 1, 2, …) von Objekten enthält, die auch Einträge, Dateneinträge, Elemente, Datenelemente, Komponenten, Unterobjekte oder nicht-statische Felder des Verbundobjekts genannt werden.
Es handelt sich hierbei um Objekte, die als Teile eines anderen Objektes auftreten. Sie müssen nicht alle denselben Typ haben, sondern können unterschiedliche Typen haben.
Wir sprechen hier von einem Verbundobjekt, weil es Verbundobjekt oft mehrere Objekte miteinander zu einem Objekt verbindet (nämlich zu einem Verbund [also einer Verbindung ] von Objekten – dem Verbundobjekt).
Es handelt sich bei den Unterobjekten sozusagen um Variablen, die in einer anderen Variablen (dem Verbundobjekt) enthalten sind.
Manchmal vergleicht man ein Objekt auch mit einem Datensatz und bezeichnet die Einträge dann als Felder.
Objekte können also Felder enthalten, in die man schreiben und aus denen man lesen kann.
Ein Verbund kennt für jedes seiner Unterobjekte einen Typ und einen Namen. Deswegen sagen wir auch, daß die Unterobjekte im Objekt eingetragen seien und bezeichnen sie als Einträge des Objekts.
Die folgende Abbildung zeigt beispielsweise ein Verbundobjekt.
- Verbundobjekt und zwei int-Unterobjekte
Verbund
.----------------------------.
| .------------------------. |
| | int | |
| '------------------------' |
| .------------------------. |
| | int | |
| '------------------------' |
'----------------------------'
Ein fundamentales Objekt läßt sich hingegen nicht weiter in Unterobjekte zerlegen.
Wie viele Unterobjekt ein Verbundobjekt enthält und welche Typen und Namen diese jeweils haben, wird durch den Typ des Verbundobjekts festgelegt. Wir werden die dazugehörige Dokumentation aber erst in einer späteren Lektion behandeln.
Die Erzeugungsfunktion »::std::make_pair«
Die Erzeugungsfunktion »::std::make_pair« ergibt („erzeugt“) ein Verbundobjekt. Es enthält in dem folgenden Programm zwei Unterobjekte vom Typ »int«, welche die Werte »2« beziehungsweise »7« enthalten.
In dem folgenden Programm wird diese Erzeugungsfunktion zur Initialisierung des Objekts »compound« verwendet.
main.cpp
#include <initializer_list>
#include <ostream>
#include <iostream>
#include <utility> /* ::std::make_pair */int main()
{ auto const compound { ::std::make_pair( 2, 7 ) }; }transcript
- (keine Ausgabe)
Das obenstehende Beispielprogramm erzeugt derzeit noch keine Ausgabe, weil wir noch nicht gelernt haben, wie wir ein Verbundobjekt oder seine Einträge ausgeben.
Die Initialisierung eines Objektes mit einem Anfangswert in der Schreibweise mit geschweiften Klammern und der Aufruf einer Funktion mit zwei Zahlen als Argumenten wurde bereits im Grundkurs behandelt, so daß die Deklaration »auto p { ::std::make_pair( 0, 0 ) };« für uns nichts Neues ist.
Die beiden Argumentwerte der Erzeugungsfunktion legen die Werte der beiden Unterobjekte des Verbundobjektes fest. Die Typen der beiden Komponenten werden durch die Typen der Initialisierungsausdrücke (beide »int«) festgelegt.
Das Verbundobjekt »compound« aus dem vorangegangenen Programm enthält beispielsweise zwei Unterobjekte: »first« und »second«.
- Verbundobjekt und zwei int-Unterobjekte
compound
.----------------------------.
| |
| first |
| .------------------------. |
| | int { 2 } | |
| '------------------------' |
| |
| second |
| .------------------------. |
| | int { 7 } | |
| '------------------------' |
'----------------------------'
Die Objekte, die von der Erzeugungsfunktion »::std::make_pair« erzeugt werden, haben alle immer genau zwei Unterobjekte, deren Namen immer »first« und »second« lauten.
Der Eintragspunkt ».«
Der Eintragspunkt ».« trat wohl erstmals in der Programmiersprache PL/I und kam dann über die Programmiersprache C nach C++.
Er erlaubt es, einen Eintrag eines Objekts anzugeben, indem der Eintrag hinter einen Ausdruck für ein Verbundobjekt geschrieben wird.
So ist »compound.first« das Unterobjekt mit dem Namen »first« innerhalb des Verbundobjekts »compound«.
»compound.second« ist das Unterobjekt mit dem Namen »second« innerhalb des Verbundobjekts »compound«.
Die beiden Namen »first« und »second« sehen wir als Einträge in »compound« an und bezeichnen den Punkt daher auch als Eintragspunkt.
Das folgende Programm zeigt, daß wird die Werte in den beiden Unterobjekten auf diese Weise nun ausgeben können.
main.cpp
#include <initializer_list>
#include <ostream>
#include <iostream>
#include <utility> /* ::std::make_pair */int main()
{ auto const compound { ::std::make_pair( 8, 4 ) };
::std::cout << compound.first << '\n';
::std::cout << compound.second << '\n'; }transcript
8
4
Das folgende Programm zeigt, daß die beiden Teilobjekt auch auf der linken Seite des Gleichheitszeichens einer Zuweisung stehen können, nachdem das »const« weggelassen wurde.
main.cpp
#include <initializer_list>
#include <ostream>
#include <iostream>
#include <utility> /* ::std::make_pair */int main()
{ auto compound { ::std::make_pair( 8, 4 ) };
compound.first = 2;
compound.second = 7;
::std::cout << compound.first << '\n';
::std::cout << compound.second << '\n'; }transcript
2
7
Verbundobjekte in C++
Es ist oft relativ einfach, in C++ Verbundobjekte zu verwenden, da die Regeln für ihre Verwendung den uns schon bekannten fundamentalen Objekten, wie int-Objekten, nachempfunden sind. Teilweise ist dies einfacher als in Java, wo Verbundobjekte durch sogenannte „Referenzen“ dargestellt werden, für die andere Regeln als für die elementaren Datentypen, wie »int«, gelten.
Klassen in C++
Der Aufbau von Verbundobjekten wird durch Klassen festgelegt, für deren Entwurf und Definition man aber etwas mehr lernen muß. Deswegen behandelt dieser Kurs zunächst das einfache und für sich bereits nützliche Thema der Verwendung von Verbundobjekten bereits vorhandener Klassen.
Die bei Facebook am häufigsten verwendete Klasse ist »::std::string« (Quelle: Video: Nicholas Ormrod: “The strange details of std::string at Facebook ”, Cppcon 2016 ). Von dieser Klasse wurde schon im Grundkurs Gebrauch gemacht.
Mehrere unabhängige Verbundobjekte
Das folgende Programm zeigt das Anlegen zweier Verbundobjekte. Da jedes dieser beiden Objekte zwei int-Speicher enthält, haben wir insgesamt vier unabhängige int-Speicher zu unserer Verfügung.
main.cpp
#include <initializer_list>
#include <ostream>
#include <iostream>
#include <utility> /* ::std::make_pair */int main()
{ auto p { ::std::make_pair( 0, 0 ) };
auto q { ::std::make_pair( 0, 0 ) };p.first = 1;
p.second = 2;
q.first = 3;
q.second = 4;::std::cout << p.first << '\n';
::std::cout << p.second << '\n';
::std::cout << q.first << '\n';
::std::cout << q.second << '\n'; }transcript
1
2
3
4
Die Dokumentation der Funktion »make_pair«
Die Originaldokumentation der Funktion »make_pair« ist derzeit für uns noch nicht vollkommen verständlich. Statt dessen können bis auf weiteres die folgenden Aussagen zu dieser Funktion als Ersatz verwendet werden:
- Vor der Verwendung von »::std::make_pair« ist die Direktive »#include <utility>« am Anfang der Quelldatei nötig.
- Der Wert von »::std::make_pair( x, y )« ist ein Paar zweier Objekte mit den Typen und Werten von »x« beziehungsweise von »y«.
- Wenn »pair« ein Paar ist, dann ist »pair.first« dessen erste und »pair.second« dessen zweite Komponente.
- Falls bei der Deklaration nicht »const« verwendet wurde, können die beiden Komponenten eines Paares auch durch eine Zuweisung verändert werden.
- Der Typ der Komponenten eines Paares ist jedoch grundsätzlich nicht veränderlich.
Zustände
Verschiedene Objekt einer Klasse können sich unterscheiden, beispielsweise können zwei ::std::pair<int,int>-Paare sich in den Werten in ihren Feldern unterscheiden. Das, worin sich verschiedene Objekte einer Klasse unterscheiden können (abgesehen von ihrer Adresse), nennen wir den Zustand der Objekte. Man kann auch vom Inhalt oder Wert eines Objekts sprechen, aber jene Begriffe werden eher bei fundamentalen Objekten verwendet.
Exemplare von Klassen
Objekte einer Klasse nennen wir auch Exemplare dieser Klasse.
Die Klasse eines Exemplars ist gleichzeitig der Typ eines Ausdrucks für jenes Exemplars.
Ein Exemplar einer Klasse ist ein Objekt, dessen Eigenschaften einerseits durch seine Klasse und andererseits auch noch durch den Zustand jenes Exemplars festgelegt werden.
Nach der Deklaration »::std::string const a{ "alpha" };« hat der Name (Ausdruck) »a« als Typ die Klasse »::std::string« und als Wert ein Exemplar dieser Klasse, welches die Zeichenfoge »alpha« repräsentiert. Dieses Exemplar wurde bei der Ausführung der Definition »::std::string const a{ "alpha" };« erzeugt.
Übungsfragen
? Typen von Ausdrücken
Welche beiden Typen haben die beiden Ausdrücke »p.first« und »p.second« in dem folgenden Programm jeweils?
main.cpp
#include <initializer_list>
#include <iostream>
#include <ostream>
#include <utility> /* ::std::pair */
#include <string>using namespace ::std::literals;
int main()
{ auto p { ::std::make_pair( "abc"s, 2.0 ) };
::std::cout << p.first << '\n';
::std::cout << p.second << '\n'; }transcript
abc
2
? Werte von Ausdrücken
Welche beiden Werte gibt das folgende Programm aus?
main.cpp
#include <initializer_list>
#include <iostream>
#include <ostream>
#include <utility> /* ::std::pair */
#include <string>using namespace ::std::literals;
int main()
{ auto p { ::std::make_pair( 1, 1 ) };
auto q { ::std::make_pair( 2, 2 ) };
q.second = 4;
p.first = q.second;
::std::cout << p.first << '\n';
::std::cout << p.second << '\n'; }
Übungsaufgaben
/ Übungsaufgabe
Schreiben Sie eine Anweisung, welche die beiden Teile eines int-Paares miteinander vertauscht!
Das folgende Programm, welches jetzt »5« und »2« (in dieser Reihenfolge) ausgibt, sollte nach Einsetzen der Anweisung an der Stelle des Kommentars dann »2« und »5« (in dieser Reihenfolge) ausgeben.
main.cpp
#include <initializer_list>
#include <ostream>
#include <iostream>
#include <utility> /* ::std::pair */int main()
{ auto p { ::std::make_pair( 5, 2 ) };/* Hier die Anweisung einsetzen! */
::std::cout << p.first << '\n';
::std::cout << p.second << '\n'; }transcript
5
2
/ Übungsaufgabe
Schreiben Sie eine Anweisung, welche die Inhalte zweier int-Paare miteinander vertauscht!
Das folgende Programm, welches jetzt »3«, »4«, »1« und »2« (in dieser Reihenfolge) ausgibt, sollte nach Einsetzen der Anweisung dann »1«, »2«, »3« und »4« (in dieser Reihenfolge) ausgeben.
main.cpp
#include <initializer_list>
#include <ostream>
#include <iostream>
#include <utility> /* ::std::pair */int main()
{ auto p { ::std::make_pair( 3, 4 ) };
auto q { ::std::make_pair( 1, 2 ) };/* Hier die Anweisung einsetzen! */
::std::cout << p.first << '\n';
::std::cout << p.second << '\n';
::std::cout << q.first << '\n';
::std::cout << q.second << '\n'; }transcript
3
4
1
2
/ Übungsaufgabe
Schreiben Sie ein Programm, in dem ein Paar »o« angelegt wird, dessen erste Komponente ein int-Objekt ist und dessen zweite Komponente ein Paar zweier int-Objekte ist. Das Programm soll dann alle drei int-Unterobjekte, die in »o« enthalten sind, ausgeben. (Hier soll im gesamten Programm möglichst nur eine Variable definiert werden, nämlich »o«.)