C++ Kurs

Standard Container I

Die Themen:

stack Container

In diesem Kapitel werden wir uns vier spezielle Container der Standard-Bibliothek ansehen. Die allgemeinen Container, wie z.B. vector, folgen später.

Beginnen wir mit dem Container stack. Wie alle Standard-Bibliothek Komponenten liegt auch der stack-Container wieder im Namensraum std. Die explizite Angabe des Namensraums wurde aber in den folgenden Beispielen der Übersichtlichkeit wegen weggelassen.

Das Einsatzgebiet einer Stack-Klasse sollte inzwischen bekannt sein, zumal Sie sich in den vorangegangen Kapiteln auch schon selbst einmal an der Realisierung einer solchen Klasse versuchen durften. Trotzdem zur Erinnerung noch einmal: ein Stack dient zum vorübergehenden Abspeichern von Daten, wobei das zuletzt abgelegte Datum auch wieder als erstes ausgelesen wird. Ein Stack ist ein sogenannter LIFO-Speicher (LIFO = last in, first out).

Da wir uns hier mit der Standard-Bibliothek befassen, weist der stack Container der Standard-Bibliothek gegenüber unserer Implementierung in den vorangegangen Übungen einen wichtigen Unterschied auf: er ist als Template realisiert und damit für alle Datentypen gültig, d.h. der stack Container kann für die Ablage von beliebigen einfachen Datentypen (wie z.B. int oder double) und zur Ablage von Objekten eingesetzt werden. Die einzige Einschränkung besteht darin, dass alle in einem Stack abgelegten Daten denselben Datentyp besitzen müssen.

Für die Verwendung des stack Containers ist die Datei <stack> einzubinden.

AchtungWenn Sie Objekte in einem Stack ablegen, so sollte die entsprechende Klasse den Zuweisungsoperator, den Kopierkonstruktor, die Move-Memberfunktionen und den Destruktor definieren wenn sie nicht trivial ist. Eine nicht-triviale Klasse enthält entweder dynamische Eigenschaften oder weitere eingeschlossene Objekte. Ferner sollten die Vergleichsoperatoren < und == für die Klasse definiert sein. Diese Anforderungen an eine Klasse gelten übrigens für alle Objekte die in Containern der Standard-Bibliothek abgelegt werden sollen.

Definition

Für die Definition eines Stack-Objekts geben Sie zuerst den Containertyp stack, gefolgt von einem <...> Klammerpaar an. Innerhalb des Klammerpaars steht dann der Datentyp der Daten, die im Container abgelegt werden sollen. Im Anschluss daran folgt dann der Name des Stack-Objekts.

PgmHeader// stack für ints definieren
stack<int> intStack;
// stack für Objekte der Klasse Window definieren
stack<Window> winStack;

Der auf diese Weise definiert Stack ist natürlich noch leer.

Ablage von Elementen

Zur Ablage von Elementen auf dem Stack dient die Memberfunktion push(...). Sie erhält als Parameter das abzulegende Element. Dabei ist aber darauf zu achten, dass das abzulegende Element auch mit dem Datentyp übereinstimmt, für den der Stack definiert wurde oder sich zumindest in diesen Datentyp konvertieren lässt.

PgmHeader// int auf Stack ablegen
intStack.push(5);
// Window-Objekt auf Stack ablegen
Window myWin(...);
...
winStack.push(myWin);
AchtungLegen Sie Daten auf dem Stack ab, so wird im Stack eine Kopie des übergebenen Datums abgelegt! Nehmen Sie nach dem Ablegen des Datums noch Änderungen am Ursprungsdatum vor, so wirkt sich dies nicht auf das im Stack abgelegt Datum aus. Durch diese Eigenschaft des Stacks können Sie z.B. eine Sicherungskopie eines Datums auf dem Stack ablegen, das Datum dann verändern und später wieder mit dem ursprünglichen Datum weiterarbeiten.

Zugriff auf Elemente

Um auf das oberste Element des Stacks zuzugreifen, dient die Memberfunktion top(). top() liefert lediglich eine Referenz auf das Element zurück, d.h. das Element selbst verbleibt weiterhin auf dem Stack. Wird das zurückgegebene Element einer Variable zugewiesen, so haben Änderungen an der Variable keinen Einfluss auf das Element im Stack. Im nachfolgenden Beispiel hat daher die Änderung der Variablen val keinen Einfluss auf den im Stack abgelegten Wert. Wird dagegen die Referenz selbst abgelegt und darüber eine Änderung vorgenommen, so verändert sich dann natürlich auch das Element auf dem Stack. Inwieweit dies aber eine 'saubere' Programmierung ist sei einmal dahingestellt.

PgmHeader// zurückgegebene Referenz in Variable umspeichern
auto val = intStack.top();
// Wert verändern
val++;
// val erhält wieder ursprünglichen Wert vom Stack
val = intStack.top();
// Nun zurückgegebene Referenz abspeichern
auto& refVal = intStack.top();
// Wert über Referenz verändern
refVal++;
// Liefert jetzt veränderten Wert zurück
int newVal = intStack.top();

Sehen wir uns einmal an was passiert, wenn ein Objekt aus dem Stack ausgelesen wird. Wird die zurückgelieferte Referenz einem bereits bestehenden Objekt zugewiesen, so wird der Zuweisungsoperator der entsprechenden Klasse ausgeführt, d.h. es wird eine Kopie des obersten Stackelements erstellt. Wird dagegen die Referenz einem neuen Objekt zugewiesen, so wird anstelle des Zuweisungsoperators der Kopierkonstruktor ausgeführt. Besonders effektiv ist es, die Referenz selbst abzulegen, da hier keinerlei Kopiervorgänge stattfinden. Allerdings muss dann beachtet werden, dass Änderungen am referenzierten Objekt sich natürlich auf das im Stack abgelegte Objekt auswirken.

PgmHeader// Objekt direkt auslesen
// Führt zum Aufruf des Zuweisungsoperators der Klasse Window

Window winOne(...);
winOne = winStack.top();
// Hier wird der copy-ctor aufgerufen
Window winTwo(winStack.top());
// Referenz ablegen
// Weitere Veränderungen von winRef wirken das Stackelement

auto& winRef = winStack.top();

Zum Entfernen des obersten Stackelements dient die Memberfunktion pop(). Der Aufruf dieser Memberfunktion führt bei einem Stack mit Objekten auch zum Aufruf des Destruktors des entfernten Objekts.

PgmHeader// Obersten Wert vom Stack entfernen
intStack.pop();
// Oberstes Objekt vom Stack entfernen
// Führt zum Aufruf des dtor von Window

winStack.pop();

AchtungWenn Sie mit der zurückgelieferten Referenz arbeiten, dann ist dies, wie erwähnt, zwar von der Laufzeit effizient, birgt aber auch eine gewisse Gefahr. Sehen Sie sich dazu das folgende Beispiel an. Zunächst wird die von top() zurückgelieferte Referenz auf das oberste Stackelement abgespeichert. Irgendwann im Verlaufe des Programms wird dieses Element dann mittels pop() vom Stack entfernt, was wiederum dazu führt, dass das entsprechende Objekt gelöscht wird.

...
// Oberstes Stackelement auslesen und als Referenz ablegen
Window& winRef = winStack.top();
...
// Oberstes Element nun entfernen
winStack.pop();
...
// Referenz verweist nun auf ein nicht vorhandenes Element! CRASH!!
winRef.Size(...);

Die durch top() zurückgelieferte Referenz verweist nach pop() damit auf ein Objekt, das nicht mehr existiert. Ein weiterer Zugriff über die Referenz führt dann im besten Fall dazu, dass das Programm einfach beendet wird.

Sonstige Memberfunktionen

Damit kennen Sie die wichtigsten Memberfunktionen des stack-Templates. Außer den bisher aufgeführten Memberfunktionen stehen u.a. noch die Memberfunktionen empty() und size() zur Verfügung. empty() liefert true, wenn der Stack keine Elemente enthält und size() die Anzahl der Elemente im Stack.

Fernen können Stacks noch mit den üblichen Vergleichsoperatoren wie z.B. == oder < verglichen werden. Sind auf einem Stack Objekte abgelegt, dann führt der Vergleich zum Aufruf der entsprechenden Vergleichs-Memberfunktionen bzw. (friend-)Vergleichsfunktionen. Der Vergleich zweier Stack-Objekte erfolgt Element für Element. Im nachfolgenden Beispiel ist damit astack kleiner als istack, da das zweite Element von istack größer ist als das zweite Element von astack.

PgmHeaderistack.push(1);
istack.push(6);

astack.push(1);
astack.push(3);
astack.push(5);

if (astack<istack)
   cout << "astack < istack\n";
else
   cout << "astack >= istack\n";

queue Container

Während beim stack Container die zuletzt abgelegten Elemente wieder zuerst ausgelesen werden (LIFO = last in, first out), werden beim queue Container die zuerst abgelegten Elemente auch wieder zuerst ausgelesen (FIFO = first in, first out). Der queue Container eignet sich daher sehr gut als Zwischenpuffer, da er die Reihenfolge der Elemente nicht umdreht.

Bei der Verwendung des queue Containers ist die Datei <queue> einzubinden.

Definition

Die Definition einer Queue erfolgt analog zur Definition eines Stacks, nur dass hier anstelle des Containers stack der queue Container angegeben wird. Die so definierte Queue enthält noch keine Elemente.

PgmHeader// queue für ints definieren
queue<int> intQueue;
// queue für Objekte der Klasse Window definieren
queue<Window> winQueue;

Ablage von Elementen

Zur Ablage von Elementen in eine Queue dient ebenfalls die Memberfunktion push(...). Sie erhält als Parameter das abzulegende Element. Und auch hier gilt: in die Queue wird lediglich eine Kopie des übergebenen Elements abgespeichert und nicht das Original.

PgmHeader// int in Queue ablegen
intQueue.push(5);
// Window-Objekt in Queue ablegen
Window myWin(...);
...
winQueue.push(myWin);

Zugriff auf Elemente

Um die in einer Queue abgelegten Elemente auszulesen, wird die Memberfunktion front() eingesetzt. Sie liefert eine Referenz auf das erste, d.h. älteste, Element in der Queue. front() entfernt das per Referenz zurückgelieferte Element nicht aus der Queue, dies muss mit einer weiteren Memberfunktion gesondert durchgeführt werden.

Außer dem Zugriff auf das älteste Element gestattet der queue Container auch den Zugriff auf das zuletzt abgelegte Element (neustes Element) über die Memberfunktion back(). back() liefert ebenfalls wie front() eine Referenz auf das Element.

PgmHeader// Ältestes Queue-Element auslesen und als Referenz ablegen
// Ablage der Referenz hier effizient, aber auch fehleranfällig!

Window& winRef = winQueue.front();
// Zuletzt abgelegten int Wert auslesen
auto newest = intQueue.back();

Bezüglich der zurückgelieferten Referenz (und den Konsequenzen daraus) gilt das Gleiche wie beim stack Container.

Zum Entfernen des ältesten Elements aus einer Queue dient die Memberfunktion pop(). Der Aufruf dieser Memberfunktion führt bei Queues mit Objekten auch zum Aufruf des Destruktors des entfernten Objekts. Es gibt aber keine Memberfunktion um das zuletzt abgelegte (neueste) Element aus einer Queue zu entfernen.

PgmHeader// Ältesten Wert aus der Queue entfernen
intQueue.pop();
// Ältestes Objekt aus der Queue entfernen
// Fuehrt zum Aufruf des dtor von Window

winQueue.pop();

Sonstige Memberfunktionen

Die restlichen Schnittstellen des queue Containers wie empty(), size() und die Vergleichsoperatoren entsprechen denen des stack Containers.

priority_queue Container

Der priority_queue Container dient ebenfalls zum Abspeichern von Elementen, wobei die Elemente beim Auslesen nach ihrer Priorität sortiert ausgegeben werden. Die Priorität wird dabei durch einen einfachen Vergleich bestimmt. Bei numerischen Werten hat ein größerer Wert standardmäßig auch eine höhere Priorität. Damit eignet sich der priority_queue Container z.B. zum einfachen Sortieren. Bei Elementen mit gleicher Priorität ist die Reihenfolge der implementierungsabhängig. Werden in einem priority_queue Container Objekte abgelegt, so muss die Klasse des Objekts zumindest den Operator < überladen damit eine Sortierung möglich ist.

Das Sortierkriterium, welches die Prioritäten-Reihenfolge festlegt, kann auch frei definiert werden. Wie dies erfolgt, sehen wir uns nachher gleich an.

Beginnen wir aber mit den einfacheren Dingen. Zunächst muss für die Verwendung des priority_queue Containers ebenfalls wieder die Datei <queue> eingebunden werden.

Definition, Ablage von Elementen usw.

Die Definition einer priority_queue erfolgt im einfachsten Fall analog zur Definition einer normalen Queue, nur dass hier jetzt als Datentyp priority_queue anstelle von queue angegeben wird. Auch die Ablage, das Auslesen und das Entfernen erfolgen mit den gleichnamigen Memberfunktionen push(...), top() und pop(). Lediglich die Memberfunktion front() existiert bei einer Prioritätsqueue nicht. Im nachfolgenden Beispiel werden zunächst drei int-Werte in der Prioritätsqueue abgelegt. Diese Werte dann aus der Prioritätsqueue nacheinander ausgelesen. Wie der Programmausgabe entnommen werden kann, werden die Werte mit fallender Priorität (Wertigkeit) ausgegeben.

Ebenfalls definiert der priority_queue Container die Memberfunktionen size() und empty(), welche funktional den gleichnamigen Memberfunktionen der normalen Queue entsprechen.

PgmHeader// using für Priority-Queue
using queueType = priority_queue<int>;
// Priority-Queue definieren
queueType myPrio;
// Werte ablegen
myPrio.push(10);
myPrio.push(5);
myPrio.push(20);
// Werte der Priorität nach auslesen
while (!myPrio.empty())
{
   cout <<  myPrio.top() << ',';
   myPrio.pop();
}
Programmausgabe20,10,5,

Sehen wir uns noch ein weiteres Beispiel für den Einsatz des priority_queue Containers an. Da die Ablage von einfachen Datentypen in einer Prioritätsqueue wohl eher seltener vorkommt, wollen wir uns im folgenden Beispiel einmal die Ablage von Aktienkursen in einer solchen Queue ansehen. Die Aktienkurse sollen dabei nach fallendem Wert ausgegeben werden, d.h. als 'Priorität' soll der aktuelle Kurs dienen.

Ein Aktienkurs setzt sich zusammen aus dem Namen eines Unternehmens und dem aktuellen Wert der Aktie. Und damit kann der Aktienkurs in einem pair abgelegt werden. Da die Sortierreihenfolge der Queue nach dem Aktienwert erfolgen soll, muss als erstes Element des pair auch der Aktienwert stehen. Denken Sie beim Einsatz eines pair immer daran, dass das erste Element eines pair immer Vorrang beim Vergleich hat. Würde als erstes pair-Element der Name des Unternehmens stehen, so würde die Prioritätsqueue die Unternehmen alphabetisch sortieren.

Für das pair bietet es sich an, mittels using ein entsprechendes Symbol zu definieren. Dieses Symbol wird dann bei der Definition der Prioritätsqueue angegeben.

PgmHeader// pair für Aktienkurs
using stock = pair<double,string>;
// Prioritätsqueue für Aktienkurs
priority_queue<stock> stockQueue;

Danach kann die Prioritätsqueue mit beliebig vielen Aktienkursen 'gefüllt' werden.

PgmHeader// Prioritätsqueue mit Aktienkursen belegen
stockQueue.push(stock(11.11,"One"));
stockQueue.push(stock(44.44,"Four"));
stockQueue.push(stock(22.22,"Two"));

Das Auslesen der Aktienkurse erfolgt wieder mit den Befehlen top() und pop(). Beachten Sie, dass die Prioritätsqueue keine Operationen kennt, mit denen die Queue durchlaufen werden kann ohne Elemente zu löschen. Für solche Anwendungsfälle gibt es andere Container, die wir uns später ansehen werden.

PgmHeader// Ausgabe der Aktienkurse
while (!stockQueue.empty())
{
   auto actStock = stockQueue.top();
   cout << "Unternehmen: " << actStock.second << "\Aktienkurs: "
        << actStock.first << endl;
   stockQueue.pop();
}

Sortierkriterium für priority_queue

Standardmäßig werden die Elemente in einer Prioritätsqueue fallend sortiert. Soll die Sortierung in einer anderen Reihenfolge erfolgen, so kann das Sortierkriterium hierfür explizit festgelegt werden

Um das Sortierkriterium festzulegen, muss ein entsprechender Funktionsoperator (functor) zur Verfügung gestellt werden. Funktionsoperatoren kennen ja schon aus dem Kapitel Überladen von speziellen Operatoren.

Der für den Vergleich verwendete functor erhält die beiden zu vergleichenden Elemente per const-Referenz übergeben und liefert als Returnwert einen bool-Wert zurück. Ist der Rückgabewert des functors true, so wird in der Queue zuerst das im zweiten Parameter übergebene Element abgelegt dann das erste Element. Bei Rückgabe von false wird die Ablagereihenfolge umgedreht. Aber sehen wir uns dies anhand des Beispiels zur Ablage von Aktienkursen einmal an. Diesmal sollen die Aktienkurse in aufsteigender Reihenfolge (also der umgekehrten Standard-Priorität) abgelegt werden.

Für die Definition des functors definieren wir die Klasse CompareStocks. Da die Aktienkurse als stock Datentyp (definiert mittels using Anweisung) abgelegt werden, erhält der functor die zu vergleichenden Elemente auch als stock Parameter übergeben. Um die Aktienkurse in aufsteigender Reihenfolge in der Prioritätsqueue abzulegen, müssen die beiden übergebenen Elemente in umgekehrter Reihenfolgt abgelegt werden. D.h. der functor muss dann true zurückliefern wenn gilt, s1.first > s2.first (first ist der Kurswert der Aktie im pair!).

PgmHeader// Funktionsobjekt
struct CompareStocks
{
   bool operator()(const stock& s1, const stock& s2) const
   {
      return (s1.first > s2.first);
   }
};

Soll bei der vorgegebenen pair-Struktur zum Beispiel die priority_queue nach Unternehmensnamen sortiert werden, so sind anstelle der pair Elemente first lediglich die pair Elemente second zu vergleichen. Solche eigen definierten Sortierkriterien gestatten eine sehr flexible Anwendung der Prioritätsqueue.

HinweisBeachten Sie, dass für den functor hier der Klassentyp struct verwendet wurde! Der überladene Funktionsoperator muss natürlich public sein, damit der priority_queue Container darauf auch Zugriff hat.

Ist der functor definiert, so muss er noch mit der priority_queue verbunden werden. Dies geschieht folgendermaßen:

Zuerst ist ein entsprechendes Objekt der functor-Klasse zu definieren. Und dieses functor-Objekt ist dann dem Konstruktor des Prioritätsqueue Objekts als zusätzlicher Parameter zu übergeben.

PgmHeader#include <vector>
#include <queue>
....
CompareStocks cmpFunc;    // Definition der Funktionsobjekts
priority_queue<stock, std::vector<stock>, CompareStocks> stockQueue(cmpFunc);

Innerhalb der spitzen Klammer steht zunächst (wie gewohnt) der Datentyp der Elemente der Prioritätsqueue. Danach folgt der Container-Typ, der intern für die Prioritätsqueue verwendet wird. Hier sollte bis auf Weiteres die Angabe std::vector<DTYP> stehen, wobei DTYP mit dem Datentyp der Elemente der Prioritätsqueue übereinstimmen muss. Zum Schluss folgt noch die Klasse des functors. Beachten Sie, dass nun in jedem Fall auch die Header-Datei <vector> einzubinden ist.

Wenn die Prioritätsqueue dann wie nachfolgend angegeben gefüllt wird, so erhalten wir die darunter stehende Ausgabe. Die Ausgabe erfolgt nun in aufsteigender Sortierung nach Aktienkursen, d.h. die Priorität wurde umgedreht.

PgmHeaderstockQueue.push(stock(11.11,"One"));
stockQueue.push(stock(44.44,"Four"));
stockQueue.push(stock(22.22,"Two"));
ProgrammausgabeCompany: One value: 11.11
Company: Two value: 22.22
Company: Four value: 44.44
HinweisDer priority_queue Container enthält noch weitere Konstruktore, die es erlauben, eine Prioritätsqueue bei ihrer Definition gleich mit Elementen zu belegen. Da diese Konstruktore aber Iteratoren verwenden und wir für deren Einsatz noch nicht die notwendigen Grundlagen haben, sei an dieser Stelle auf die Online-Hilfe verwiesen. Iteratoren werden später noch erläutert.

bitset Container

Das Klassen-Template bitset dient zum Abspeichern von Bitfolgen, d.h. Folgen von '0' und '1'. Die Anzahl der Bits in der Folge ist nicht an irgendwelche Größen der Standard-Datentypen gebunden. So können in einem bitset zum Beispiel 21 oder sogar 10000 Bits abgespeichert werden. Da ein Bit entweder gesetzt sein kann (1-Wert) oder nicht (0-Wert), werden bitset-Objekte in der Regel zum Abspeichern von sogenannten Flags eingesetzt. Aber bitset-Objekte können auch noch für einen anderen Zweck eingesetzt werden, den wir uns am Ende dieses Kapitels noch ansehen.

Wenn bitsets verwendet werden, so ist die Header-Datei <bitset> einzubinden.

Definition

Um ein bitset Objekt zu definieren, wird nach dem Namen des Klassen-Templates bitset in spitzen Klammern die Anzahl der Bits für das bitset-Objekt angegeben. Bei einem solchermaßen definierten bitset sind zu Beginn alle Bits auf 0 gesetzt.

PgmHeader// Definition eines einfachen bitset-Objekts mit 50 Bits, alle auf 0 gesetzt
bitset<50> myFlags;

Ein bitset kann bei seiner Definition auch gleich initialisiert werden. Dies geschieht durch Aufruf des entsprechenden Konstuktors. Dabei stehen folgende Möglichkeiten zur Verfügung:

  1. Durch Angabe eines unsigned long long Wertes bei der Definition des bitset-Objekts (myFlags im nachfolgenden Beispiel).
  2. Es wird ein string Objekt mit entsprechenden 0/1 Folgen definiert und dieses bei der Objektdefinition des bitset übergeben. Dabei kann dann entweder der komplette String (yourFlags) oder auch nur ein Teilstring (lowFlags/highFlags) in die entsprechenden Bits umwandelt werden. Wird nur ein Teilstring ins bitset übernommen, so gibt der erste Wert nach dem String die Startposition innerhalb des Strings an und der zweite Wert die Anzahl der zu übernehmenden Zeichen. Dabei ist aber zu beachten, dass das niederwertigste Bit das letzte Zeichen im string ist (siehe auch 'Inhalte der bitsets' im Beispiel).
PgmHeader// bitset mit 8 Bits mit dem Wert 7 (00000111) initialisieren
bitset<8> myFlags(7ULL);
// String mit 0/1 Folge, 8 Bits
string stringFlags("10010011");
// String in Bits 'umwandeln'
bitset<8> yourFlags(stringFlags);
// niederwertige Flags extrahieren (0011)
bitset<4> lowFlags(stringFlags,0,4);
// höherwertige Flags extrahieren (1001)
bitset<4> highFlags(stringFlags,4,4);
// bitset mit C-String
bitset<6> sixBits("001100"); 

Ausgabe eines bitsets

Werden bitset ausgegeben, z.B. mit den Stream cout, so wird der Inhalt des bitset als 0/1 Folgen ausgegeben. Es werden dabei aber immer alle Bits eines bitset ausgegeben, d.h. bei einem bitset mit z.B. 50 Bits werden auch alle 50 Bits ausgegeben. Und ein bitset kann sogar eingelesen werden, z.B. mit den Stream cin. Die Eingabe darf dabei natürlich nur aus 0/1 Kombinationen bestehen. Ein Beispiel hierzu folgt weiter unten.

bitset Operationen

Das Klassen-Template bitset definierte eine relativ große Anzahl von Operationen, um die Bits in einem bitset zu verarbeiten. Wir werden uns an dieser Stelle nur die wichtigsten ansehen. Für die restlichen Operationen sei wieder auf die Online-Hilfe verwiesen.

Die Memberfunktion size() liefert die Anzahl der Bits in einem bitset als size_t Wert zurück. Zum Prüfen, ob Bits in einem bitset gesetzt sind, wird die Memberfunktion any() aufgerufen. Sie liefert true zurück wenn mindestens ein Bit gesetzt ist. Noch genauer Angaben liefert die Memberfunktion count(). Sie liefert die Anzahl der gesetzten Bits zurück (siehe auch Programmausgabe).

PgmHeaderbitset<8> myFlags(string("10100011"));
cout << "size:" << myFlags.size() << ',' << "any:"
     << myFlags.any() << ',' << "count:" << myFlags.count() << endl;
Programmausgabesize:8,any:1,count:4

Mit Hilfe der Memberfunktion test(...) kann der Zustand eines bestimmten Bits innerhalb des bitsets abgefragt werden. test(...) erhält als Parameter den Index des abzuprüfenden Bits und liefert true zurück, wenn das entsprechende Bit gesetzt ist (siehe auch Programmausgabe).

PgmHeaderbitset<8> myFlags(string("10100011"));
for (size_t i=0; i<myFlags.size(); i++)
   cout << (myFlags.test(i) ? 'S' : '.');
ProgrammausgabeSS...S.S

Zum Setzen, Löschen und Invertieren von Bits dienen die Memberfunktionen set(...), reset(...) und flip(...). Diese Memberfunktionen erhalten als Parameter den Index des zu manipulierenden Bits. Alle drei Memberfunktionen haben noch weitere Formen um z.B. alle Bits in eine bitset gleichzeitig manipulieren zu können.

PgmHeaderbitset<50> manyBits(0x5FFul);
// höchstwertigstes Bit setzen
manyBits.set(49);
// niederwertigstes Bit löschen
manyBits.reset(0);
// Bit 30 invertieren
manyBits.flip(30);

Als Alternative für den Zugriff auf ein bestimmtes Bit (anstelle von test(...), set(...) und reset(...)) kann auch der Indexoperator [] verwendet werden.

PgmHeaderbitset<8> myFlags(0x07ul);
// höchstwertiges Bit setzen
myFlags[7] = 1;

Nachfolgend ist nochmals ein etwas komplexeres Beispiel für den Einsatz eines bitsets dargestellt. Das bitset besteht dabei aus 4 Flags. Je nach Zustand der Flags wird dann im Programm der auszugebende Text gesteuert. Dazu werden den Flags mittels enum-Konstanten zuerst symbolische Namen zugewiesen. So steuert im Beispiel das erste Flag FARBE die Farbe für eine Ausgabe. Nach der Definition des bitset-Objekts flags werden in diesem bitset zwei beliebig Bits gesetzt, einmal durch die set(...) Memberfunktion und einmal über den Indexoperator. Im Anschluss daran wird je nach Zustand der vier Flags (Bits) ein entsprechender Text ausgegeben.

PgmHeader// Flagnummern als enums
enum choices {FARBE, FAHRZEUG, GESCHW, STRECKE};
// bitset mit 4 Flags
bitset<4> flags;
// Zwei beliebige Flags setzen
flags.set(rand()%4);
flags[rand()%4] = 1;
// Je nach Flag entsprechenden Text ausgeben
cout << "Das " << ((flags[FARBE])?"rote":"gelbe");
cout << ((flags[FAHRZEUG])?" Fahrrad ":" Auto ");
cout << "faehrt " << ((flags[GESCHW])?"schnell":"langsam");
cout << " um die " << ((flags[STRECKE])?"Kurve":"Ecke") << endl;
ProgrammausgabeDas gelbe Fahrrad faehrt langsam um die Kurve
HinweisDas Klassen-Template bitset löst bei diversen unerlaubten Operationen Ausnahmen aus. So führt zum Beispiel das Setzen des Bits 8 mittels set() in einem bitset mit 8 Bits (Bitnummern: 0...7) zum Auslösen der Ausnahme out_of_range. Da wir zum jetzigen Zeitpunkt noch keine Ausnahmen verarbeiten können, führt dies zum Beenden des Programms.

Aber außer zum Abspeichern von einzelnen Bits kann ein bitset auch zum Konvertieren einer in einem string abgelegten 0/1 Folge in einen numerischen Wert oder umgekehrt eingesetzt werden. Dazu stellt bitset die beiden Memberfunktionen to_ulong() und to_string() zur Verfügung. Sehen wir uns beide Memberfunktionen am besten anhand eines Beispiels an.

PgmHeader// bitset mit 8 Bits definieren
bitset<8> toNumeric;
// bitset einlesen, als 0/1 Folge
cin >> toNumeric;
// 0/1 Folge als numerischen Wert ausgeben
cout << "Eingelesener Wert: " << toNumeric.to_ulong() << endl;
// bitset mit 01010101 initialiseren
bitset<8> toString(0x55);
// 0/1 Folge in string umwandeln
string converted =
     toString.to_string<char,char_traits<char>, allocator<char>>();
cout << "Als string: " << converted << endl;

Im Beispiel wird zuerst ein bitset mit 8 Bits definiert. Dieses bitset wird dann mittels cin eingelesen (nur 0 und 1 als Eingabe zugelassen!). Anschließend wird die eingelesene 0/1 Folge mit der Memberfunktion to_long() in einen unsigned long Wert umgewandelt und dann ausgegeben. Danach wird ein weiteres bitset definiert, welches bei seiner Definition mit dem Wert 0x55 initialisiert wird. Um die im bitset abgelegten Bits in einen 0/1 String zu konvertieren, wird die Memberfunktion to_string() aufgerufen. Beachten Sie bitte den etwas 'kryptischen' Aufruf der Memberfunktion. to_string() ist als Funktions-Template definiert und erfordert deshalb die angegebene aufwändige Schreibweise.

Damit wollen wir an dieser Stelle die Behandlung des bitsets beenden. Wie bereits erwähnt, besitzt das Klassen-Template noch eine Reihe weiterer Operatoren wie z.B. <<= um alle Bits eines bitsets um eine bestimmte Anzahl von Stellen nach links zu schieben.

Beispiel und ÜbungUnd hier geht's zum Beispiel und zur Übung.