Positionen in C++
Unter einer Folge von Objekten stellen wir uns eine Menge von Objekten vor, die linear angeordnet sind. Man denke etwa an eine Zeichenfolge. Dabei hat jedes Objekt der Folge einen Vorgänger und einen Nachfolger, und jedes Objekt kann von jedem anderen aus durch wiederholtes Gehen zum Nachfolger oder Vorgänger erreicht werden; ausnahmsweise darf es ein Objekt geben, das keinen Vorgänger hat, und ein Objekt, das keinen Nachfolger hat. (Dies beschreibt allerdings nur unsere Vorstellung einer Folge, es muß nicht immer möglich sein, alle eben beschriebenen Operationen auch in einem C++ -Programm durchzuführen.)
In C++ kann eine Folge beispielsweise aus einem Objekt der Klasse »::std::string«, der Klasse »::std::vector<T>« oder der Klasse »::std::initializer_list<T>« gewonnen werden – alles Klassen für Objekte, die eine Anordnung von Dingen darstellen. Aber auch eine Reihung kann eine Folge liefern.
Im folgenden Beispiel einer Folge hat die linke Komponente keinen Vorgänger und die rechte keinen Nachfolger. Der Nachfolger der linken Komponente ist die mittlere Komponente und der Nachfolger der mittleren Komponente ist die rechte Komponenten. Der Vorgänger der mittlere Komponente ist die linke Komponente und der Vorgänger der rechten Komponente ist die mittlere Komponenten.
- Eine Folge von drei Objekten
.---. .---. .---.
| a | | b | | a |
'---' '---' '---'(links Mitte rechts)
Die dargestellte Folge könnte in einem Programm praktisch durch das Zeichenfolgenliteral »"aba"s« dargestellt werden. Beispiele für Folgen in C++ sind sind Zeichenfolgen, Vektoren und Reihungen.
Man sieht, daß es nicht ganz einfach ist, sich auf eine Position in einer Folge zu beziehen. Wir können zwar von „der Komponente »b«“ sprechen, aber nicht von „der Komponente »a«“, da es zwei Komponenten mit dem Buchstaben »a« gibt. Wir können die Komponenten von Zeichenfolgen, Vektoren und Reihungen zwar als durchnumeriert ansehen, aber nicht alle Folgen in C++ erlauben es, eine Kennzahl zu verwenden, um eine bestimmte Komponente auszudrücken. Daher gibt es in C++ spezielle Werte, um Positionen in einer Folge auszudrücken.
Eine Position gibt eine Position in einer Folge von Objekten an.
Der Anfang »cbegin«
Die Funktion »cbegin« ergibt die Position des Anfangs einer Folge von Objekten. Wir benötigen »#include <iterator>«.
main.cpp
#include <string>
#include <iterator> /* cbegin */
#include <initializer_list>using namespace ::std::literals;
int main()
{ ::std::string const str{ "abc"s };
auto const p0 = cbegin( str ); }- Protokoll
- (keine Ausgabe)
Die Position »p0« gibt jetzt die Position des »a« an.
Wir werden erst etwas später sehen, wofür Positionen verwendet werden können.
Man beachte die Verwendung von ADL in »cbegin( str )«. Der Typ des Argumentausdrucks ist »::std::string«. Der Name jenes Typs befindet sich im Namensraum »::std«. Daher wird in »::std« nach »cbegin« gesucht und es muß nicht »::std::cbegin( str )« geschrieben werden.
Folgen
Es gibt Typen, deren Ausdrücke die nicht als Argument von »::std::cbegin« verwendet werden können.
main.cpp
#include <iterator>
int main()
{ auto const p0 = ::std::cbegin( 2 ); }- Protokoll (überarbeitet und übersetzt)
main.cpp: In Funktion 'int main()':
main.cpp:4:36: Fehler: keine passende Funktion für den Aufruf '::std::cbegin(int)'
{ auto const p0 = ::std::cbegin( 2 ); }
^
Wir nennen einen Ausdruck, der als Argument von »::std::cbegin« verwendet werden kann, einen Folgenausdruck, entsprechend ist sein Typ ein Folgentyp und der Wert des Ausdrucks eine Folgenobjekt. Wenn keine Mißverständnisse zu befürchten sind, können alle drei aber auch einfach als eine Folge bezeichnet werden.
Eine Zeichenfolge ist also eine Folge, während eine Zahl keine Folge ist.
Ein anderes mögliches, etwas technischeres Adjektiv für eine Folge wäre „const-iterabel “ (englisch: “const-iterable ”).
Ein Objekt mit einem Typ aus der Standardbibliothek ist eine Folge, wenn es entweder eine Reihung ist oder eine Elementfunktion »begin()« enthält.
Der Deckel »cend«
In C++ gibt es zu einer Folge von Objekten mehr Positionen als Objekte! Es gibt zu jedem Objekt eine Position, welche sich „direkt hinter“ jenem Objekt befindet, und zwar auch für das letzte Objekt der Folge, obwohl sich hinter diesem gar kein Objekt mehr befindet.
- Positionen hinter Objekten
0 1 2 3 Objekte
.---. .---. .---.
| a | | b | | a |
'---' '---' '---'
| ^ | ^ | ^
'---' '---' '---' Weg zum Nachfolger^ ^ ^ ^
0 1 2 3 4 Positionen
Die Position direkt hinter dem letzten Objekt einer Folge von Objekten bezeichnen wir auch als den Deckel jener Folge. Der Deckel gibt kein Objekt der Folge an, es ist eine Position ohne Objekt.
Die Funktion »cend« ergibt die Position des Deckels einer Objektfolge.
main.cpp
#include <iostream>
#include <ostream>
#include <string>
#include <iterator>
#include <initializer_list>using namespace ::std::literals;
int main()
{ ::std::string s = "abc"s;
auto const pt = cend( s ); }transcript
Die Position »pt« gibt jetzt die Position „direkt hinter“ dem Buchstaben »c« an.
Wir werden erst etwas später sehen, wofür Positionen verwendet werden können.
- Anfang und Deckel
.---. .---. .---.
| a | | b | | c |
'---' '---' '---'^ ^
cbegin cend
Positionen in Reihungen
Auch für Reihungen können Positionen erhalten werden, doch da hier das ADL nicht greift, muß dann wieder »::std« als Namensraum angegeben werden.
main.cpp
#include <initializer_list>
int main()
{ int a[]{ 1, 2, 3 };
{ auto const p0 = ::std::cbegin( a ); }
{ auto const pt = ::std::cend( a ); }}transcript
Durch eine Verwendung von »using« können die Schreibweisen vereinheitlicht werden. (»using« kann auch in einem Block verwendet werden und gilt dann für jenen Block.)
main.cpp
#include <iostream>
#include <ostream>
#include <string>
#include <iterator>
#include <initializer_list>using namespace ::std::literals;
int main()
{ using ::std::cbegin;
using ::std::cend;{ ::std::string s = "abc"s;
auto const p0 = cbegin( s ); }{ int a[]{ 1, 2, 3 };
auto const p1 = cbegin( a ); }}transcript
Nicht-konstante Positionen (Schreibpositionen)
Die von uns oben verwendeten „konstanten“ Positionen erlauben es nicht, unter Verwendung jener Positionen in Objekt zu schreiben, dafür sind „nicht-konstante“ Indikator zu verwenden, die mit »begin« und »end« erhalten werden.
Konstante Positionen werden von »::std::cbegin« und »::std::cend« geliefert, nichtkonstante von »::std::begin« und »::std::end«.
main.cpp
#include <initializer_list>
#include <iostream>
#include <iterator>
#include <ostream>
#include <string>using namespace ::std::literals;
int main()
{ ::std::string s = "abc"s;
{ auto const p0 = begin( s ); }
auto const pt = end( s ); }}transcript
Positionen in Vektoren
Auch für Vektoren können Positionen erhalten werden.
main.cpp
#include <initializer_list>
#include <vector>int main()
{ ::std::vector< int >v { 0, 1, 2 };
{ auto const p0 = cbegin( v ); }
{ auto const pt = cend( v ); }
{ auto const p0 = begin( v ); }
{ auto const pt = end( v ); }}transcript
Positionen in Initialisierungslistenobjekten
Auch für Initialisierungslistenobjekte können Positionen erhalten werden.
main.cpp
#include <initializer_list>
#include <ostream>
#include <iostream>
#include <string>int main()
{ ::std::initializer_list< char > l { 'a', 'b', 'c' };
{ auto const p0 = cbegin( l ); }
{ auto const pt = cend( l ); }
{ auto const p0 = begin( l ); }
{ auto const pt = end( l ); } }transcript
Das Initialisierungslistenobjekte sonst wenig Zugriffsmöglichkeiten bieten (es gibt keine at-Funktion zum Zugriff auf eine Komponente), sind die Positionsfunktionen für Initialisierungslistenobjekte besonders wichtig. Wir werden allerdings erst etwas später sehen wie jene Funktionen eingesetzt werden können.
Bereiche
Ein Paar von Positionen gibt einen Bereich an, wenn sie Positionen in ein und derselbe Liste sind und die zweite Position hinter der ersten liegt (also durch wiederholtes Gehen zum Nachfolger von der ersten Position aus erreichbar ist). Beispiel: Das Paar »cbegin( str )« und »cend( str )« gibt einen Bereich an.
Wir nennen
- Ein Bereich von drei Objekten (»abc«)
.---. .---. .---.
| a | | b | | c |
'---' '---' '---'^ ^
'-----------------'
cbegin cend
Der Deckel des Bereichs (die zweite Komponente des Bereichs) gilt nicht mehr als Teil des Bereichs, er liegt hinter dem Bereich!
Das Paar aus der Position des »a« und der Position des »c« in der Zeichenfolge »abc« umfaßt beispielsweise nur zwei Objekte, nämlich »a« und »b«. Der Deckel, also die Position des »c« gehört nicht mehr dazu.
- Ein Bereich von zwei Objekten (»ab«)
.---. .---. .---.
| a | | b | | c |
'---' '---' '---'^ ^
'------------'
Bereiche werden oft verwendet, um Folgen von Objekten auf eine abstrakte Weise zu beschreiben, wie wir etwas später sehen werden.
- Ein Bereich mit einem Objekt (»a«)
.---. .---. .---.
| a | | b | | c |
'---' '---' '---'^ ^
'------'
Bei einem leeren Bereich ist die erste Position gleich der zweiten Position
- Ein leerer Bereich (»«)
.---. .---. .---.
| a | | b | | c |
'---' '---' '---'^
'
Wir werden erst etwas später sehen, wofür Bereiche verwendet werden können.