SlideShare ist ein Scribd-Unternehmen logo
Kurs: Effektive Software-Entwicklung mit C++ (http://tutego.de/g/CPP2)
Autor: Boris Schäling

Kapitel 7: Der C++ Standard
Inhaltsverzeichnis

   •   7.1 Allgemeines
   •   7.2 Strings
   •   7.3 Streams
   •   7.4 Container
   •   7.5 Iteratoren
   •   7.6 Algorithmen

7.1 Allgemeines. Standardisierte Werkzeuge auf allen
Plattformen
In den 90er Jahren wurde die Programmiersprache C++ standardisiert. Ein internationales
Gremium arbeitete an einem langen Dokument, in dem festgehalten wurde, wie
standardisierter C++-Code auszusehen hat. Die Standardisierung sollte es ermöglichen, C++-
Code problemlos mit Compilern verschiedener Hersteller zu übersetzen, und das
idealerweise auch auf unterschiedlichen Plattformen. 1998 wurde die erste Version des C++
Standards schließlich verabschiedet und veröffentlicht.

Im C++ Standard wurde nicht nur festgelegt, aus welchen Schlüsselwörtern C++ besteht und
welche Bedeutung sie haben. Es wurden darüber hinaus Klassen und Funktionen definiert,
die von jedem Compilerhersteller, der den C++ Standard unterstützen möchte, zur
Verfügung gestellt werden müssen. Das erst macht es möglich, auf Klassen wie std::string
zugreifen zu können, ohne sich jeweils Gedanken machen zu müssen, wo diese Klasse genau
herkommt und wie sie im Detail aussieht. Der C++ Standard verlangt, dass die Klasse
std::string in einer Headerdatei string zur Verfügung gestellt wird und sich so verhält, wie
es im C++ Standard definiert ist.

Der C++ Standard hat sich soweit durchgesetzt, dass ihn heute kein Compilerhersteller
ignorieren kann. Das bedeutet, dass standardisierter C++-Code mit jedem modernen C++-
Compiler auf jeder beliebigen Plattform kompiliert werden kann. Dabei kann problemlos auf
sämtliche Klassen und Funktionen zugegriffen werden, die im Namensraum std definiert
sind. Alle in diesem Namensraum vorhandenen Klassen und Funktionen müssen sich so
verhalten, wie es der C++ Standard vorschreibt - egal, mit welchem Compiler und egal, auf
welcher Plattform Sie arbeiten.

In diesem Kapitel erhalten Sie einen Überblick über verschiedene Bestandteile des C++
Standards. Neben der Klasse std::string und den Streams std::cin und std::cout, die Sie
bereits in vielen Beispielen kennengelernt und eingesetzt haben, werden Ihnen Container,
Iteratoren und Algorithmen vorgestellt.
7.2 Strings. Zeichenketten speichern und verarbeiten
Eine der am häufigsten verwendeten im C++ Standard definierten Klassen ist std::string.
Diese Klasse ist in der Headerdatei string definiert und bietet rund 100 Methoden, um
Zeichenketten zu verarbeiten.

#include <string>
#include <cstdio>

int main()
{
  std::string s = "Hello";
  s += ", world!";
  std::printf("%sn", s.c_str());

    std::string::size_type idx = s.find("world");
    if (idx != std::string::npos)
    {
      s.replace(idx, 5, "moon");
      std::printf("%sn", s.c_str());
    }
}

Im obigen Programm werden ein paar Methoden der Klasse std::string beispielhaft
verwendet. So wird zuerst die Variable s mit Hilfe eines überladenen Konstruktors mit
"Hello" initialisiert und anschließend der überladene Operator += verwendet, um ", world!"
an s anzuhängen.

Die Methode c_str() gibt einen Zeiger auf einen Puffer zurück, in dem der String mit einem
Nullzeichen abgeschlossen ist. Da Strings in C üblicherweise mit einem Nullzeichen
abgeschlossen werden, wird diese Methode immer dann verwendet, wenn ein String an eine
C-Funktion übergeben werden muss. std::printf() ist eine derartige C-Funktion, der
deswegen die Variable s nicht direkt übergeben wird, sondern mit Hilfe von c_str() ein
Zeiger auf einen mit einem Nullzeichen abgeschlossenen String. Auf diese Weise kann der
String "Hello, world!" mit std::printf() ausgegeben werden.

Die Klasse std::string bietet verschiedene Methoden an, um einen String zu durchsuchen.
So gibt find() die Position einer gefundenen Zeichenkette im String zurück. Der Datentyp
dieser Positionsnummer ist std::string::size_type. Sollte die gesuchte Zeichenkette nicht
gefunden werden, gibt find() std::string::npos zurück.

Es stehen darüber hinaus Methoden zum Ersetzen und Löschen von Zeichen zur Verfügung.
So wird im obigen Beispiel mit replace() das Wort "world" durch "moon" ersetzt.

Beachten Sie, dass std::string keine Methoden anbietet, deren Funktionsweise vom
sogenannten Kulturkreis abhängt. So gibt es beispielsweise keine Methode, um einen String
in Groß- oder Kleinbuchstaben umzuwandeln. Denn welches Zeichen jeweils wie
umgewandelt werden müsste, hängt vom Kulturkreis ab. So sind im Deutschen Umlaute wie
ä, ö und ü Buchstaben wie alle anderen. Im Englischen aber zählen ä, ö und ü nicht zu den
Buchstaben. Das Ergebnis einer Umwandlung in Groß- oder Kleinbuchstaben hängt demnach
vom Kulturkreis ab. Da std::string nur Methoden anbietet, die unabhängig vom Kulturkreis
immer gleiche Ergebnisse liefern, stehen keine Methoden zur Umwandlung von Buchstaben
zur Verfügung.

Neben std::string ist in der Headerdatei string auch eine Klasse std::wstring definiert.
Diese Klasse basiert nicht auf dem Datentyp char, sondern auf wchar_t. Der C++ Standard
legt die Größe dieses Datentyps nicht fest. Sie können lediglich davon ausgehen, dass ein
wchar_t auf 2 oder mehr Bytes basiert und damit größer als ein char ist. Das ermöglicht
eine andere Kodierung von Strings. So wird in der Praxis std::wstring üblicherweise
eingesetzt, um Unicode-Strings zu speichern.

#include <string>
#include <cstdio>

int main()
{
  std::wstring s = L"Hello";
  s += L", world!";
  std::printf("%lsn", s.c_str());

    std::string::size_type idx = s.find(L"world");
    if (idx != std::string::npos)
    {
      s.replace(idx, 5, L"moon");
      std::printf("%lsn", s.c_str());
    }
}

Obiges Programm verwendet Strings vom Typ std::wstring. Da die Zeichen aller Strings
nicht mehr auf einem char, sondern auf einem wchar_t basieren, muss vor Literalen ein L
angegeben werden. So muss bei der Initialisierung von s L"Hello" angegeben werden, um
dem Compiler zu verstehen zu geben, dass die fünf Zeichen jeweils auf einem wchar_t
basieren.

Abgesehen von der Kennzeichnung der Literale mit einem L und dem neuen
Formatierungssymbol %ls, das zur Ausgabe von auf wchar_t basierenden Strings mit
std::printf() verwendet werden muss, gibt es keine Änderungen im Vergleich zum
vorherigen Beispiel. Der einzige Unterschied zwischen std::string und std::wstring ist die
Kodierung der Strings.

7.3 Streams. Auf dem Flusskonzept basierende
Schnittstellen
Unter Streams können Sie sich vereinfacht Rohre vorstellen, über die Daten von einer Quelle
gelesen oder in ein Ziel geschrieben werden. Die Schnittstelle ist dabei so abstrakt, dass sich
die unterschiedlichsten Objekte wie Streams verhalten können. Die Bedingungen, die
Streams an eine Quelle oder ein Ziel stellen, sind minimal. Streams erwarten lediglich, dass
ein paar Methoden zur Verfügung stehen, mit denen Daten gelesen oder geschrieben
werden können.
Zwei Streams kennen Sie bereits aus vielen Beispielen: std::cin ist die Standardeingabe und
std::cout die Standardausgabe. Es handelt sich dabei um Objekte, die in jedem C++
Programm automatisch vorhanden sind, wenn iostream eingebunden wird.

#include <string>
#include <iostream>

int main()
{
  std::string s;
  int i;

    std::cin >> s >> i;
    std::cout << i << s;
}

Über den Operator >> können Daten von std::cin gelesen werden. Um Daten auf std::cout
auszugeben, wird auf den Operator << zugegriffen. Man bezeichnet diese Lese- und
Schreiboperationen als formatierte Ein- und Ausgabe, da Daten automatisch umgewandelt
werden. So wird im obigen Programm bei einer Eingabe automatisch die erste Zeichenkette
in s gespeichert und die zweite Zeichenkette in einen Wert vom Typ int umgewandelt, um
ihn dann in i zu speichern. Bei der Ausgabe über std::cout geschieht ähnliches.

Neben der formatierten Ein- und Ausgabe unterstützen Streams auch nicht-formatierte
Lese- und Schreiboperationen.

#include <iostream>
#include <cstring>

int main()
{
  char buffer[32];
  std::cin.getline(buffer, sizeof(buffer));
  std::cout.write(buffer, std::strlen(buffer));
}

Über die Methode getline() kann eine Zeile komplett in einen Puffer geladen werden. Die
Methode erkennt das Zeilenende am Enter, das der Anwender eingeben muss, damit der
Aufruf von getline() zurückkehrt. Es findet dabei im Gegensatz zur formatierten
Dateneingabe keine Interpretation der eingegebenen Zeichen statt - die gesamte Zeile wird
in den Puffer kopiert. getline() setzt automatisch ans Ende der gelesenen Zeichen ein
Nullzeichen, so dass der String im Puffer zum Beispiel an eine C-Funktion übergeben werden
kann.

So wie Daten über getline() unformatiert von der Standardeingabe gelesen werden können,
können sie mit write() unformatiert auf die Standardausgabe ausgegeben werden.

Objekte wie std::cin und std::cout sind prominente, aber nicht die einzigen Streams, die in C+
+-Programmen verwendet werden können. Während diese beiden Objekte automatisch
existieren, stellt der C++ Standard zahlreiche Stream-Klassen zur Verfügung, die von Ihnen
instantiiert werden können. Dabei wird grundsätzlich zwischen Ein- und Ausgabestreams
unterschieden. Eingabestreams sind von std::istream abgeleitet, Ausgabestreams von
std::ostream. In diesen beiden Klassen sind auch die verschiedenen Methoden definiert,
die für die formatierte und nicht-formatierte Ein- und Ausgabe von Daten verwendet werden
können. Weil alle Streams von mindestens einer der beiden Klassen abgeleitet sind, besitzen
sie alle die gleiche Schnittstelle.

Der C++ Standard bietet zum Beispiel zwei Klassen std::istringstream und
std::ostringstream an, die in der Headerdatei sstream definiert sind. Diese Klassen
verwenden als Quelle bzw. Ziel einen String vom Typ std::string. Mit Hilfe dieser beiden
Klassen können Daten von einem String so gelesen und in einen String so geschrieben
werden wie Sie es von std::cin und std::cout gewohnt sind.

#include <string>
#include <sstream>
#include <iostream>

int main()
{
  std::string input = "abc 10";
  std::istringstream is(input);
  std::ostringstream os;

    std::string s;
    int i;

    is >> s >> i;
    os << i << s;

    std::cout << os.str() << std::endl;
}

Im obigen Programm wird ein Eingabestream is vom Typ std::istringstream mit dem String
input initialisiert. Der Stream is verwendet demnach den String input als Quelle. Es werden
dann mit Hilfe des Operators >> Daten formatiert gelesen - zuerst ein String, dann eine Zahl.
Diese Daten werden in den Variablen s und i entgegen genommen.

Im nächsten Schritt werden die Daten in s und i in umgekehrter Reihenfolge in den Stream
os geschrieben. Dieser Stream ist ein Ausgabestream vom Typ std::ostringstream. Alle
Daten, die in einen derartigen Stream geschrieben werden, werden intern in einer Variablen
vom Typ std::string gespeichert. Die Methode str() gibt diesen internen String zurück. Im
obigen Programm wird str() verwendet, um den String zur Kontrolle auf die
Standardausgabe auszugeben.

Der C++ Standard stellt weitere Stream-Klassen zur Verfügung wie beispielsweise
std::ifstream und std::ofstream, die in der Headerdatei fstream definiert sind. Diese
Klassen können verwendet werden, um Daten aus Dateien zu lesen bzw. in Dateien zu
schreiben. Es existiert außerdem eine Klasse std::fstream, die sowohl von std::istream als
auch von std::ostream abgeleitet ist und somit Lese- und Schreiboperationen unterstützt.

7.4 Container. Gruppieren von Daten
Ein wichtiger Bestandteil des C++ Standards sind verschiedene Container, die es einfach
machen, große Datenmengen zu verwalten. Für größtmögliche Flexibilität sind alle
Containerklassen als Templates definiert, so dass sie Daten beliebigen Typs speichern
können. So existiert beispielsweise eine Klasse std::vector, die sich grundsätzlich wie ein
Array verhält. Der Vorteil dieser Klasse ist jedoch, dass die Größe von Vektoren im Gegensatz
zu Arrays dynamisch ist und Elemente beliebig hinzugefügt und entfernt werden können.

#include <vector>
#include <iostream>

int main()
{
  std::vector<char> v;
  v.push_back('a');
  v.push_back('b');
  v.push_back('c');

    std::cout << v[0] << std::endl;
    std::cout << v.at(2) << std::endl;
    std::cout.write(&v[0], v.size()) << std::endl;
}

Im obigen Programm wird ein Vektor v verwendet, der Elemente vom Typ char speichern
kann. Um den Vektor mit drei Buchstaben zu füllen, wird auf die Methode push_back()
zugegriffen.

Um auf ein Element an einer bestimmten Position im Vektor zuzugreifen, kann entweder der
überladene Operator [] oder die Methode at() verwendet werden. Der Unterschied ist: at()
überprüft, ob der übergebene Index gültig ist und wirft im Fehlerfall eine Ausnahme vom Typ
std::out_of_range. Der Operator [] hingegen überprüft wie bei herkömlichen Arrays nicht,
ob der Index innerhalb der gültigen Bandbreite liegt.

Vektoren garantieren, dass Elemente direkt hintereinander im Speicher liegen - so wie Sie es
von herkömlichen Arrays kennen. Deswegen können wie im obigen Programm geschehen
mit Hilfe der Methode write() die Elemente im Vektor als Zeichenkette "abc" auf die
Standardausgabe ausgegeben werden. Die Anzahl der Elemente im Vektor wird über die
Methode size() ermittelt.

Da Vektoren garantieren, dass Elemente direkt hintereinander im Speicher liegen, kann das
Hinzufügen eines neuen Elements mit push_back() dazu führen, dass ein Vektor einen
neuen Speicherbereich reservieren muss, der für die aktuell gespeicherten Daten und das
neu hinzugefügte Elemente groß genug ist. Das bedeutet, dass momentan im Vektor
gespeicherte Daten in einen neuen Speicherbereich kopiert werden müssen.

Wenn es für Ihr Programm wichtig ist, dass Daten schnell hinzugefügt werden können, ohne
das bereits gespeicherte Daten intern an eine andere Position kopiert werden müssen,
bietet sich die Liste an. Die entsprechende Klasse heißt std::list und ist in der Headerdatei
list definiert.

#include <list>
#include <iostream>
int main()
{
  std::list<char> l;
  l.push_back('a');
  l.push_back('b');
  l.push_back('c');

    std::cout << l.front() << std::endl;
    std::cout << l.back() << std::endl;
    std::cout << l.size() << std::endl;
}

Der entscheidende Unterschied zwischen einer Liste und einem Vektor ist, dass in einer Liste
ein Element mit Hilfe eines Zeigers auf das nächste Element zeigt, während bei einem Vektor
alle Elemente direkt hintereinander in einem Speicherbereich liegen. Weil in einer Liste
Elemente über Zeiger gekoppelt sind, kann das Hinzufügen eines neuen Elements nicht dazu
führen, dass die Liste bereits gespeicherte Daten verschieben muss. Da Entwickler in
unterschiedlichen Situationen unterschiedliche Container benötigen, bietet der C++
Standard entsprechend verschiedene Klassen an, aus denen gewählt werden kann.

Während der Zugriff auf ein beliebiges Element in einem Array und in einem Vektor über
Zeigerarithmetik sehr schnell vonstatten geht, muss sich eine Liste anhand der Zeiger über
die Elemente entlang hangeln. Deswegen bietet std::list keinen überladenen Operator []
und keine Methode at() an. Sie müssen sich also selbst anhand der Zeiger vorhangeln, um zu
einem Element an einer bestimmten Position in der Liste zu kommen. Mit front() und
back() stehen aber zwei Methoden zur Verfügung, um direkt auf das erste und letzte
Element in einer Liste zugreifen zu können.

Neben std::vector und std::list bietet der C++ Standard weitere Container wie
std::deque, std::queue und std::set an. Alle Container sind auf bestimmte
Datenstrukturen und Algorithmen zur Verwaltung von Daten spezialisiert, so dass Entwickler
den für ihre Situation jeweils besten Container wählen können.

Neben den bisher kennengelernten Containern, die einfach nur Gruppen von Daten
speichern, gibt es einen Container, in dem jeweils zwei Daten eineinander zugeordnet sind.

#include    <string>
#include    <map>
#include    <utility>
#include    <iostream>

int main()
{
  std::map<std::string, int> m;
  m.insert(std::make_pair("Anton", 35));
  m.insert(std::make_pair("Boris", 31));
  m.insert(std::make_pair("Caesar", 40));

    std::cout << m["Boris"] << std::endl;
}

Die Klasse std::map ist ein Template, das zwei Parameter erwartet - nämlich die Typen der
Daten, die eineinander zugeordnet werden sollen. Wenn wie im obigen Beispiel Name und
Alter von Personen zugeordnet werden sollen, kann std::map mit std::string und int
instantiiert werden. In diesem Fall ist der String der Schlüssel und die Zahl der dem Schlüssel
zugeordnete Wert.

Container vom Typ std::map sortieren Daten anhand der Schlüssel. Dank dieser
automatischen Sortierung ist es möglich, Werte über Schlüssel sehr schnell zu finden. So
wird im obigen Programm mit Hilfe des überladenen Operators [] nach dem Schlüssel
"Boris" gesucht und der dem Schlüssel zugeordnete Wert 31 auf die Standardausgabe
ausgegeben.

std::map bietet zwar einen Operator [] an. Das heißt aber nicht, dass Daten intern ähnlich
wie bei Arrays oder Vektoren gemeinsam in einem Speicherbereich liegen. Im Gegensatz zu
Arrays und Vektoren garantieren Container vom Typ std::map noch nicht einmal, dass
Daten intern genau in der Reihenfolge gespeichert werden, in der sie dem Container mit
insert() hinzugefügt wurden. Damit Container vom Typ std::map Schlüssel schnell finden
können, müssen sie Daten sortieren. Sie sollten demnach std::map nicht verwenden, wenn
Ihnen die Reihenfolge der Daten, die Sie im Container speichern, wichtig ist und die
Reihenfolge unverändert bleiben muss.

7.5 Iteratoren. Zeiger auf Elemente in Containern
Alle Container bieten zwei Methoden begin() und end() an, die sogenannte Iteratoren
zurückgeben. Es handelt sich dabei um Objekte, mit denen über Elemente in einem
Container iteriert werden kann. Der von begin() zurückgegebene Iterator zeigt dabei auf
das erste Element, der von end() zurückgegebene Iterator auf die Position hinter dem
letzten gültigen Element in einem Container.

Iteratoren sind daher etwas ähnliches wie Zeiger. Sie bieten tatsächlich einen überladenen
Operator * an, so dass wie bei Zeigern über das Sternchen auf ein Element zugegriffen
werden kann, auf das ein Iterator verweist.

#include <list>
#include <iostream>

int main()
{
  std::list<char> l;
  l.push_back('a');
  l.push_back('b');
  l.push_back('c');

    for (std::list<char>::iterator it = l.begin(); it != l.end(); ++it)
     std::cout << *it << std::endl;
}

Im obigen Beispiel wird auf die Liste zugegriffen, die bereits in einem Beispiel im vorherigen
Abschnitt verwendet wurde. Mit Hilfe der Iteratoren wird nun über die einzelnen Elemente
in der Liste iteriert, um dann über das Sternchen auf das jeweilige Element zuzugreifen und
es auf die Standardausgabe auszugeben.
Die Schleifenkonstruktion, die Sie im obigen Programm sehen, ist typisch: Mit begin() wird
ein Iterator auf das erste Element des Containers erhalten. Bevor über diesen Iterator
versucht werden darf, auf ein Element im Container zuzugreifen, wird überprüft, ob er
ungleich dem Iterator ist, der von end() zurückgegeben wird. Nach jedem
Schleifendurchgang wird der Iterator mit ++ inkrementiert, um ein Element im Container
vorzurücken.

Der Datentyp des Iterators hängt mit dem Datentyp des Containers zusammen, über dessen
Elemente der Iterator laufen soll. Alle Container definieren dazu einen Typ iterator, so dass
es nicht notwendig ist, sich neue Klassennamen zu merken.

Mit Hilfe der Iteratoren ist es nun möglich, auf jedes beliebige Element in einem Container
zuzugreifen. Wenn Sie beispielsweise das zweite Element der Liste ausgeben möchten,
können Sie das wie folgt machen.

#include <list>
#include <utility>
#include <iostream>

int main()
{
  std::list<char> l;
  l.push_back('a');
  l.push_back('b');
  l.push_back('c');

    std::list<char>::iterator it = l.begin();
    std::advance(it, 1);
    std::cout << *it << std::endl;
}

In der Headerdatei utility ist eine Funktion std::advance() definiert, mit der ein Iterator
um eine bestimmte Anzahl an Stellen vor- oder zurückgesetzt werden kann. Im obigen
Beispiel wird der Iterator, der auf das erste Element im Container zeigt, um eine Stelle
vorgerückt und zeigt somit auf das zweite Element im Container. Genausogut hätte in
diesem Fall der Iterator natürlich auch mit ++ inkrementiert werden können. Für größere
Sprünge ist std::advance() jedoch hilfreich.

Der eigentliche Grund, warum es Iteratoren gibt, liegt in ihrer Eigenschaft als Bindeglied
zwischen Containern und Algorithmen. So stehen im C++ Standard zahlreiche Algorithmen
zur Verfügung, mit denen Daten in Containern verarbeitet werden können. Diese
Algorithmen erwarten jedoch als Parameter keine Container, sondern Iteratoren. Auf diese
Weise können Algorithmen auf bestimmte Elemente in Containern angewandt werden und
müssen nicht sämtliche Elemente in einem Container verarbeiten.

7.6 Algorithmen. Mit Elementen in Containern arbeiten
Der C++ Standard definiert gut 60 Algorithmen in der Headerdatei algorithm. Es handelt
sich dabei um freistehende Funktionen, die mindestens zwei Iteratoren als Parameter
erwarten: Ein Iterator, der auf den Anfang einer Sequenz von Elementen zeigt, und einer,
der dahinter zeigt. Wenn Sie einen Algorithmus auf alle Elemente in einem Container
anwenden möchten, können Sie als Parameter die Iteratoren übergeben, die von begin()
und end() zurückgegeben werden.

#include <list>
#include <algorithm>
#include <iostream>

int main()
{
  std::list<char> l;
  l.push_back('a');
  l.push_back('b');
  l.push_back('c');

    std::reverse(l.begin(), l.end());
    for (std::list<char>::iterator it = l.begin(); it != l.end(); ++it)
     std::cout << *it << std::endl;
}

Im obigen Beispiel wird der Algorithmus std::reverse() verwendet, der die Reihenfolge der
Elemente zwischen den beiden Iteratoren, die als Parameter übergeben werden, umdreht.

#include <list>
#include <algorithm>
#include <iostream>

int main()
{
  std::list<char> l;
  l.push_back('a');
  l.push_back('b');
  l.push_back('c');

    std::list<char>::iterator it = l.begin();
    std::advance(it, 1);
    std::rotate(l.begin(), it, l.end());
    for (std::list<char>::iterator it = l.begin(); it != l.end(); ++it)
     std::cout << *it << std::endl;
}

Der Algorithmus std::rotate() ist eine Funktion, die als Parameter drei Iteratoren erwartet.
std::rotate() rotiert die Elemente, auf die die Iteratoren zeigen, im Kreis. Der mittlere
Iterator bestimmt dabei das Element, das nach der Rotation das erste Element sein soll.

#include <list>
#include <algorithm>
#include <iostream>

int main()
{
  std::list<char> l;
  l.push_back('a');
  l.push_back('b');
  l.push_back('c');

    std::cout << std::count(l.begin(), l.end(), 'c') << std::endl;
}
Mit Hilfe des Algorithmus std::count() kann die Anzahl der Elemente zwischen zwei
Iteratoren gefunden werden, die mit dem Wert, der als dritter Parameter übergeben wird,
identisch sind.

Beachten Sie, dass die Iteratoren, die Sie als Parameter an Algorithmen übergeben, immer
auf Elemente im gleichen Container verweisen müssen. Es hat unvorhersehbare Folgen und
kann zu einem Absturz führen, wenn Sie Iteratoren übergeben, die auf Elemente in
unterschiedlichen Containern verweisen.

Weitere ähnliche Inhalte

Andere mochten auch

Fundamentos
FundamentosFundamentos
Fundamentos
JMILER
 
Im Schnittfeld von Schul , Lern- und Medienkulturen- Medienbildung in der Schule
Im Schnittfeld von Schul , Lern- und Medienkulturen- Medienbildung in der SchuleIm Schnittfeld von Schul , Lern- und Medienkulturen- Medienbildung in der Schule
Im Schnittfeld von Schul , Lern- und Medienkulturen- Medienbildung in der Schule
Benjamin Jörissen
 
Las ti cs en la formación docente
Las ti cs en la formación docenteLas ti cs en la formación docente
Las ti cs en la formación docente
David066
 
Ennergia Solar informe de
Ennergia Solar  informe deEnnergia Solar  informe de
Ennergia Solar informe de
ines micaela santana
 
Akteure
AkteureAkteure
Akteure
Martin Rehm
 
Extinción de los animales Diverticomputo
Extinción de los animales DiverticomputoExtinción de los animales Diverticomputo
Extinción de los animales Diverticomputo
Amelia Acuña
 
Metodo ipler
Metodo ipler Metodo ipler
Metodo ipler
Fabian Arce
 
Medioambiente 091017093820-phpapp01
Medioambiente 091017093820-phpapp01Medioambiente 091017093820-phpapp01
Medioambiente 091017093820-phpapp01
Noe Contreras Sifuentes
 
Arbeitsmarkt in oe 2010_odp
Arbeitsmarkt in oe 2010_odpArbeitsmarkt in oe 2010_odp
Arbeitsmarkt in oe 2010_odp
AUGE
 
Soziales Kapital als Möglichkeit zur Analyse von Twitter-Konversationen und „...
Soziales Kapital als Möglichkeit zur Analyse von Twitter-Konversationen und „...Soziales Kapital als Möglichkeit zur Analyse von Twitter-Konversationen und „...
Soziales Kapital als Möglichkeit zur Analyse von Twitter-Konversationen und „...
Martin Rehm
 
Weather window by weather bug
Weather window by weather bugWeather window by weather bug
Weather window by weather bug
jorgesalomonjs
 
Víctor jara
Víctor jaraVíctor jara
Víctor jara
Marc Crosas
 
Teoria del color final
Teoria del color finalTeoria del color final
Teoria del color final
oscardiaz2409
 
Un minuto
Un minutoUn minuto
Un minuto
Rosita Condoy
 
Circuitos
CircuitosCircuitos
Circuitos
juanpablo1994
 
Tics
TicsTics
Historia del internet
Historia del internetHistoria del internet
Historia del internet
tauro25
 
Grup 5 futures educadores (1)
Grup 5 futures educadores (1)Grup 5 futures educadores (1)
Grup 5 futures educadores (1)
futureseducadores
 

Andere mochten auch (19)

Fundamentos
FundamentosFundamentos
Fundamentos
 
Im Schnittfeld von Schul , Lern- und Medienkulturen- Medienbildung in der Schule
Im Schnittfeld von Schul , Lern- und Medienkulturen- Medienbildung in der SchuleIm Schnittfeld von Schul , Lern- und Medienkulturen- Medienbildung in der Schule
Im Schnittfeld von Schul , Lern- und Medienkulturen- Medienbildung in der Schule
 
Las ti cs en la formación docente
Las ti cs en la formación docenteLas ti cs en la formación docente
Las ti cs en la formación docente
 
Ennergia Solar informe de
Ennergia Solar  informe deEnnergia Solar  informe de
Ennergia Solar informe de
 
Akteure
AkteureAkteure
Akteure
 
LAS VOCALES
LAS VOCALESLAS VOCALES
LAS VOCALES
 
Extinción de los animales Diverticomputo
Extinción de los animales DiverticomputoExtinción de los animales Diverticomputo
Extinción de los animales Diverticomputo
 
Metodo ipler
Metodo ipler Metodo ipler
Metodo ipler
 
Medioambiente 091017093820-phpapp01
Medioambiente 091017093820-phpapp01Medioambiente 091017093820-phpapp01
Medioambiente 091017093820-phpapp01
 
Arbeitsmarkt in oe 2010_odp
Arbeitsmarkt in oe 2010_odpArbeitsmarkt in oe 2010_odp
Arbeitsmarkt in oe 2010_odp
 
Soziales Kapital als Möglichkeit zur Analyse von Twitter-Konversationen und „...
Soziales Kapital als Möglichkeit zur Analyse von Twitter-Konversationen und „...Soziales Kapital als Möglichkeit zur Analyse von Twitter-Konversationen und „...
Soziales Kapital als Möglichkeit zur Analyse von Twitter-Konversationen und „...
 
Weather window by weather bug
Weather window by weather bugWeather window by weather bug
Weather window by weather bug
 
Víctor jara
Víctor jaraVíctor jara
Víctor jara
 
Teoria del color final
Teoria del color finalTeoria del color final
Teoria del color final
 
Un minuto
Un minutoUn minuto
Un minuto
 
Circuitos
CircuitosCircuitos
Circuitos
 
Tics
TicsTics
Tics
 
Historia del internet
Historia del internetHistoria del internet
Historia del internet
 
Grup 5 futures educadores (1)
Grup 5 futures educadores (1)Grup 5 futures educadores (1)
Grup 5 futures educadores (1)
 

Ähnlich wie Der C++ Standard

BIT I WiSe 2014 | Basisinformationstechnologie I - 08: Programmiersprachen I
BIT I WiSe 2014 | Basisinformationstechnologie I - 08: Programmiersprachen IBIT I WiSe 2014 | Basisinformationstechnologie I - 08: Programmiersprachen I
BIT I WiSe 2014 | Basisinformationstechnologie I - 08: Programmiersprachen I
Institute for Digital Humanities, University of Cologne
 
C++11 und c++14
C++11 und c++14C++11 und c++14
C++11 und c++14
Holger Jakobs
 
OSMC 2010 | Logverarbeitung mit syslog-ng - Status und Zukunft by Martin Grauel
OSMC 2010 | Logverarbeitung mit syslog-ng - Status und Zukunft by Martin GrauelOSMC 2010 | Logverarbeitung mit syslog-ng - Status und Zukunft by Martin Grauel
OSMC 2010 | Logverarbeitung mit syslog-ng - Status und Zukunft by Martin Grauel
NETWAYS
 
Java Streams und Lambdas
Java Streams und LambdasJava Streams und Lambdas
Java Streams und Lambdas
Nane Kratzke
 
Ctypes
CtypesCtypes
C++ kompakt
C++ kompaktC++ kompakt
Java 8, mit Lambdas das hohe Lied des Functional Programming singen
Java 8, mit Lambdas das hohe Lied des Functional Programming singenJava 8, mit Lambdas das hohe Lied des Functional Programming singen
Java 8, mit Lambdas das hohe Lied des Functional Programming singen
gedoplan
 
Addressroom
AddressroomAddressroom
Addressroom
joerg89
 
BASTA! 2017 Jubiläumskonferenz - Warum warten auf die IDE!?
BASTA! 2017 Jubiläumskonferenz - Warum warten auf die IDE!?BASTA! 2017 Jubiläumskonferenz - Warum warten auf die IDE!?
BASTA! 2017 Jubiläumskonferenz - Warum warten auf die IDE!?
Robin Sedlaczek
 
Typescript
TypescriptTypescript
Typescript
Sebastian Springer
 
Graphiz - Using the DOT Language
Graphiz - Using the DOT LanguageGraphiz - Using the DOT Language
Graphiz - Using the DOT Language
albazo
 
FH Wedel - SS11 - Seminar - Marcus Riemer - LEDA
FH Wedel - SS11 - Seminar - Marcus Riemer - LEDAFH Wedel - SS11 - Seminar - Marcus Riemer - LEDA
FH Wedel - SS11 - Seminar - Marcus Riemer - LEDA
Marcus Riemer
 
Workshop: Besseres C#
Workshop: Besseres C#Workshop: Besseres C#
Workshop: Besseres C#
Rainer Stropek
 
Interprozesskommunikation mit PHP
Interprozesskommunikation mit PHPInterprozesskommunikation mit PHP
Interprozesskommunikation mit PHP
Stephan Schmidt
 
C/ C++ for Notes & Domino Developers
C/ C++ for Notes & Domino DevelopersC/ C++ for Notes & Domino Developers
C/ C++ for Notes & Domino Developers
Ulrich Krause
 

Ähnlich wie Der C++ Standard (17)

BIT I WiSe 2014 | Basisinformationstechnologie I - 08: Programmiersprachen I
BIT I WiSe 2014 | Basisinformationstechnologie I - 08: Programmiersprachen IBIT I WiSe 2014 | Basisinformationstechnologie I - 08: Programmiersprachen I
BIT I WiSe 2014 | Basisinformationstechnologie I - 08: Programmiersprachen I
 
C++11 und c++14
C++11 und c++14C++11 und c++14
C++11 und c++14
 
OSMC 2010 | Logverarbeitung mit syslog-ng - Status und Zukunft by Martin Grauel
OSMC 2010 | Logverarbeitung mit syslog-ng - Status und Zukunft by Martin GrauelOSMC 2010 | Logverarbeitung mit syslog-ng - Status und Zukunft by Martin Grauel
OSMC 2010 | Logverarbeitung mit syslog-ng - Status und Zukunft by Martin Grauel
 
Java Streams und Lambdas
Java Streams und LambdasJava Streams und Lambdas
Java Streams und Lambdas
 
Ctypes
CtypesCtypes
Ctypes
 
C++ kompakt
C++ kompaktC++ kompakt
C++ kompakt
 
Java 8, mit Lambdas das hohe Lied des Functional Programming singen
Java 8, mit Lambdas das hohe Lied des Functional Programming singenJava 8, mit Lambdas das hohe Lied des Functional Programming singen
Java 8, mit Lambdas das hohe Lied des Functional Programming singen
 
Addressroom
AddressroomAddressroom
Addressroom
 
BASTA! 2017 Jubiläumskonferenz - Warum warten auf die IDE!?
BASTA! 2017 Jubiläumskonferenz - Warum warten auf die IDE!?BASTA! 2017 Jubiläumskonferenz - Warum warten auf die IDE!?
BASTA! 2017 Jubiläumskonferenz - Warum warten auf die IDE!?
 
Typescript
TypescriptTypescript
Typescript
 
Bit WiSe 2013 | Basisinformationstechnologie I - 08: Programmiersprachen I
Bit WiSe 2013 | Basisinformationstechnologie I - 08: Programmiersprachen IBit WiSe 2013 | Basisinformationstechnologie I - 08: Programmiersprachen I
Bit WiSe 2013 | Basisinformationstechnologie I - 08: Programmiersprachen I
 
Graphiz - Using the DOT Language
Graphiz - Using the DOT LanguageGraphiz - Using the DOT Language
Graphiz - Using the DOT Language
 
FH Wedel - SS11 - Seminar - Marcus Riemer - LEDA
FH Wedel - SS11 - Seminar - Marcus Riemer - LEDAFH Wedel - SS11 - Seminar - Marcus Riemer - LEDA
FH Wedel - SS11 - Seminar - Marcus Riemer - LEDA
 
Workshop: Besseres C#
Workshop: Besseres C#Workshop: Besseres C#
Workshop: Besseres C#
 
Interprozesskommunikation mit PHP
Interprozesskommunikation mit PHPInterprozesskommunikation mit PHP
Interprozesskommunikation mit PHP
 
C/ C++ for Notes & Domino Developers
C/ C++ for Notes & Domino DevelopersC/ C++ for Notes & Domino Developers
C/ C++ for Notes & Domino Developers
 
Messen mit LabVIEW- Block 3
Messen mit LabVIEW- Block 3Messen mit LabVIEW- Block 3
Messen mit LabVIEW- Block 3
 

Mehr von tutego

CSS Seminar
CSS SeminarCSS Seminar
CSS Seminar
tutego
 
Schulung jQuery JavaScript-Bibliothek
Schulung jQuery JavaScript-BibliothekSchulung jQuery JavaScript-Bibliothek
Schulung jQuery JavaScript-Bibliothek
tutego
 
Schulung C++ Boost Bibliotheken
Schulung C++ Boost BibliothekenSchulung C++ Boost Bibliotheken
Schulung C++ Boost Bibliotheken
tutego
 
SQL-Updates mit der JDBC-API
SQL-Updates mit der JDBC-APISQL-Updates mit der JDBC-API
SQL-Updates mit der JDBC-API
tutego
 
Die JSTL Tag-Library
Die JSTL Tag-LibraryDie JSTL Tag-Library
Die JSTL Tag-Library
tutego
 
Fundamentale Muster in Java
Fundamentale Muster in JavaFundamentale Muster in Java
Fundamentale Muster in Java
tutego
 
JdbcTemplate aus Spring
JdbcTemplate aus SpringJdbcTemplate aus Spring
JdbcTemplate aus Spring
tutego
 
Schieberegler und analoge Anzeigen in Swing
Schieberegler und analoge Anzeigen in Swing Schieberegler und analoge Anzeigen in Swing
Schieberegler und analoge Anzeigen in Swing
tutego
 
Einführung in den EventBus
Einführung in den EventBusEinführung in den EventBus
Einführung in den EventBus
tutego
 
Erweiteres for (foreach) in Java 5
Erweiteres for (foreach) in Java 5Erweiteres for (foreach) in Java 5
Erweiteres for (foreach) in Java 5
tutego
 
Autoboxing in Java 5
Autoboxing in Java 5Autoboxing in Java 5
Autoboxing in Java 5
tutego
 

Mehr von tutego (12)

Klassen
KlassenKlassen
Klassen
 
CSS Seminar
CSS SeminarCSS Seminar
CSS Seminar
 
Schulung jQuery JavaScript-Bibliothek
Schulung jQuery JavaScript-BibliothekSchulung jQuery JavaScript-Bibliothek
Schulung jQuery JavaScript-Bibliothek
 
Schulung C++ Boost Bibliotheken
Schulung C++ Boost BibliothekenSchulung C++ Boost Bibliotheken
Schulung C++ Boost Bibliotheken
 
SQL-Updates mit der JDBC-API
SQL-Updates mit der JDBC-APISQL-Updates mit der JDBC-API
SQL-Updates mit der JDBC-API
 
Die JSTL Tag-Library
Die JSTL Tag-LibraryDie JSTL Tag-Library
Die JSTL Tag-Library
 
Fundamentale Muster in Java
Fundamentale Muster in JavaFundamentale Muster in Java
Fundamentale Muster in Java
 
JdbcTemplate aus Spring
JdbcTemplate aus SpringJdbcTemplate aus Spring
JdbcTemplate aus Spring
 
Schieberegler und analoge Anzeigen in Swing
Schieberegler und analoge Anzeigen in Swing Schieberegler und analoge Anzeigen in Swing
Schieberegler und analoge Anzeigen in Swing
 
Einführung in den EventBus
Einführung in den EventBusEinführung in den EventBus
Einführung in den EventBus
 
Erweiteres for (foreach) in Java 5
Erweiteres for (foreach) in Java 5Erweiteres for (foreach) in Java 5
Erweiteres for (foreach) in Java 5
 
Autoboxing in Java 5
Autoboxing in Java 5Autoboxing in Java 5
Autoboxing in Java 5
 

Der C++ Standard

  • 1. Kurs: Effektive Software-Entwicklung mit C++ (http://tutego.de/g/CPP2) Autor: Boris Schäling Kapitel 7: Der C++ Standard Inhaltsverzeichnis • 7.1 Allgemeines • 7.2 Strings • 7.3 Streams • 7.4 Container • 7.5 Iteratoren • 7.6 Algorithmen 7.1 Allgemeines. Standardisierte Werkzeuge auf allen Plattformen In den 90er Jahren wurde die Programmiersprache C++ standardisiert. Ein internationales Gremium arbeitete an einem langen Dokument, in dem festgehalten wurde, wie standardisierter C++-Code auszusehen hat. Die Standardisierung sollte es ermöglichen, C++- Code problemlos mit Compilern verschiedener Hersteller zu übersetzen, und das idealerweise auch auf unterschiedlichen Plattformen. 1998 wurde die erste Version des C++ Standards schließlich verabschiedet und veröffentlicht. Im C++ Standard wurde nicht nur festgelegt, aus welchen Schlüsselwörtern C++ besteht und welche Bedeutung sie haben. Es wurden darüber hinaus Klassen und Funktionen definiert, die von jedem Compilerhersteller, der den C++ Standard unterstützen möchte, zur Verfügung gestellt werden müssen. Das erst macht es möglich, auf Klassen wie std::string zugreifen zu können, ohne sich jeweils Gedanken machen zu müssen, wo diese Klasse genau herkommt und wie sie im Detail aussieht. Der C++ Standard verlangt, dass die Klasse std::string in einer Headerdatei string zur Verfügung gestellt wird und sich so verhält, wie es im C++ Standard definiert ist. Der C++ Standard hat sich soweit durchgesetzt, dass ihn heute kein Compilerhersteller ignorieren kann. Das bedeutet, dass standardisierter C++-Code mit jedem modernen C++- Compiler auf jeder beliebigen Plattform kompiliert werden kann. Dabei kann problemlos auf sämtliche Klassen und Funktionen zugegriffen werden, die im Namensraum std definiert sind. Alle in diesem Namensraum vorhandenen Klassen und Funktionen müssen sich so verhalten, wie es der C++ Standard vorschreibt - egal, mit welchem Compiler und egal, auf welcher Plattform Sie arbeiten. In diesem Kapitel erhalten Sie einen Überblick über verschiedene Bestandteile des C++ Standards. Neben der Klasse std::string und den Streams std::cin und std::cout, die Sie bereits in vielen Beispielen kennengelernt und eingesetzt haben, werden Ihnen Container, Iteratoren und Algorithmen vorgestellt.
  • 2. 7.2 Strings. Zeichenketten speichern und verarbeiten Eine der am häufigsten verwendeten im C++ Standard definierten Klassen ist std::string. Diese Klasse ist in der Headerdatei string definiert und bietet rund 100 Methoden, um Zeichenketten zu verarbeiten. #include <string> #include <cstdio> int main() { std::string s = "Hello"; s += ", world!"; std::printf("%sn", s.c_str()); std::string::size_type idx = s.find("world"); if (idx != std::string::npos) { s.replace(idx, 5, "moon"); std::printf("%sn", s.c_str()); } } Im obigen Programm werden ein paar Methoden der Klasse std::string beispielhaft verwendet. So wird zuerst die Variable s mit Hilfe eines überladenen Konstruktors mit "Hello" initialisiert und anschließend der überladene Operator += verwendet, um ", world!" an s anzuhängen. Die Methode c_str() gibt einen Zeiger auf einen Puffer zurück, in dem der String mit einem Nullzeichen abgeschlossen ist. Da Strings in C üblicherweise mit einem Nullzeichen abgeschlossen werden, wird diese Methode immer dann verwendet, wenn ein String an eine C-Funktion übergeben werden muss. std::printf() ist eine derartige C-Funktion, der deswegen die Variable s nicht direkt übergeben wird, sondern mit Hilfe von c_str() ein Zeiger auf einen mit einem Nullzeichen abgeschlossenen String. Auf diese Weise kann der String "Hello, world!" mit std::printf() ausgegeben werden. Die Klasse std::string bietet verschiedene Methoden an, um einen String zu durchsuchen. So gibt find() die Position einer gefundenen Zeichenkette im String zurück. Der Datentyp dieser Positionsnummer ist std::string::size_type. Sollte die gesuchte Zeichenkette nicht gefunden werden, gibt find() std::string::npos zurück. Es stehen darüber hinaus Methoden zum Ersetzen und Löschen von Zeichen zur Verfügung. So wird im obigen Beispiel mit replace() das Wort "world" durch "moon" ersetzt. Beachten Sie, dass std::string keine Methoden anbietet, deren Funktionsweise vom sogenannten Kulturkreis abhängt. So gibt es beispielsweise keine Methode, um einen String in Groß- oder Kleinbuchstaben umzuwandeln. Denn welches Zeichen jeweils wie umgewandelt werden müsste, hängt vom Kulturkreis ab. So sind im Deutschen Umlaute wie ä, ö und ü Buchstaben wie alle anderen. Im Englischen aber zählen ä, ö und ü nicht zu den Buchstaben. Das Ergebnis einer Umwandlung in Groß- oder Kleinbuchstaben hängt demnach vom Kulturkreis ab. Da std::string nur Methoden anbietet, die unabhängig vom Kulturkreis
  • 3. immer gleiche Ergebnisse liefern, stehen keine Methoden zur Umwandlung von Buchstaben zur Verfügung. Neben std::string ist in der Headerdatei string auch eine Klasse std::wstring definiert. Diese Klasse basiert nicht auf dem Datentyp char, sondern auf wchar_t. Der C++ Standard legt die Größe dieses Datentyps nicht fest. Sie können lediglich davon ausgehen, dass ein wchar_t auf 2 oder mehr Bytes basiert und damit größer als ein char ist. Das ermöglicht eine andere Kodierung von Strings. So wird in der Praxis std::wstring üblicherweise eingesetzt, um Unicode-Strings zu speichern. #include <string> #include <cstdio> int main() { std::wstring s = L"Hello"; s += L", world!"; std::printf("%lsn", s.c_str()); std::string::size_type idx = s.find(L"world"); if (idx != std::string::npos) { s.replace(idx, 5, L"moon"); std::printf("%lsn", s.c_str()); } } Obiges Programm verwendet Strings vom Typ std::wstring. Da die Zeichen aller Strings nicht mehr auf einem char, sondern auf einem wchar_t basieren, muss vor Literalen ein L angegeben werden. So muss bei der Initialisierung von s L"Hello" angegeben werden, um dem Compiler zu verstehen zu geben, dass die fünf Zeichen jeweils auf einem wchar_t basieren. Abgesehen von der Kennzeichnung der Literale mit einem L und dem neuen Formatierungssymbol %ls, das zur Ausgabe von auf wchar_t basierenden Strings mit std::printf() verwendet werden muss, gibt es keine Änderungen im Vergleich zum vorherigen Beispiel. Der einzige Unterschied zwischen std::string und std::wstring ist die Kodierung der Strings. 7.3 Streams. Auf dem Flusskonzept basierende Schnittstellen Unter Streams können Sie sich vereinfacht Rohre vorstellen, über die Daten von einer Quelle gelesen oder in ein Ziel geschrieben werden. Die Schnittstelle ist dabei so abstrakt, dass sich die unterschiedlichsten Objekte wie Streams verhalten können. Die Bedingungen, die Streams an eine Quelle oder ein Ziel stellen, sind minimal. Streams erwarten lediglich, dass ein paar Methoden zur Verfügung stehen, mit denen Daten gelesen oder geschrieben werden können.
  • 4. Zwei Streams kennen Sie bereits aus vielen Beispielen: std::cin ist die Standardeingabe und std::cout die Standardausgabe. Es handelt sich dabei um Objekte, die in jedem C++ Programm automatisch vorhanden sind, wenn iostream eingebunden wird. #include <string> #include <iostream> int main() { std::string s; int i; std::cin >> s >> i; std::cout << i << s; } Über den Operator >> können Daten von std::cin gelesen werden. Um Daten auf std::cout auszugeben, wird auf den Operator << zugegriffen. Man bezeichnet diese Lese- und Schreiboperationen als formatierte Ein- und Ausgabe, da Daten automatisch umgewandelt werden. So wird im obigen Programm bei einer Eingabe automatisch die erste Zeichenkette in s gespeichert und die zweite Zeichenkette in einen Wert vom Typ int umgewandelt, um ihn dann in i zu speichern. Bei der Ausgabe über std::cout geschieht ähnliches. Neben der formatierten Ein- und Ausgabe unterstützen Streams auch nicht-formatierte Lese- und Schreiboperationen. #include <iostream> #include <cstring> int main() { char buffer[32]; std::cin.getline(buffer, sizeof(buffer)); std::cout.write(buffer, std::strlen(buffer)); } Über die Methode getline() kann eine Zeile komplett in einen Puffer geladen werden. Die Methode erkennt das Zeilenende am Enter, das der Anwender eingeben muss, damit der Aufruf von getline() zurückkehrt. Es findet dabei im Gegensatz zur formatierten Dateneingabe keine Interpretation der eingegebenen Zeichen statt - die gesamte Zeile wird in den Puffer kopiert. getline() setzt automatisch ans Ende der gelesenen Zeichen ein Nullzeichen, so dass der String im Puffer zum Beispiel an eine C-Funktion übergeben werden kann. So wie Daten über getline() unformatiert von der Standardeingabe gelesen werden können, können sie mit write() unformatiert auf die Standardausgabe ausgegeben werden. Objekte wie std::cin und std::cout sind prominente, aber nicht die einzigen Streams, die in C+ +-Programmen verwendet werden können. Während diese beiden Objekte automatisch existieren, stellt der C++ Standard zahlreiche Stream-Klassen zur Verfügung, die von Ihnen instantiiert werden können. Dabei wird grundsätzlich zwischen Ein- und Ausgabestreams unterschieden. Eingabestreams sind von std::istream abgeleitet, Ausgabestreams von
  • 5. std::ostream. In diesen beiden Klassen sind auch die verschiedenen Methoden definiert, die für die formatierte und nicht-formatierte Ein- und Ausgabe von Daten verwendet werden können. Weil alle Streams von mindestens einer der beiden Klassen abgeleitet sind, besitzen sie alle die gleiche Schnittstelle. Der C++ Standard bietet zum Beispiel zwei Klassen std::istringstream und std::ostringstream an, die in der Headerdatei sstream definiert sind. Diese Klassen verwenden als Quelle bzw. Ziel einen String vom Typ std::string. Mit Hilfe dieser beiden Klassen können Daten von einem String so gelesen und in einen String so geschrieben werden wie Sie es von std::cin und std::cout gewohnt sind. #include <string> #include <sstream> #include <iostream> int main() { std::string input = "abc 10"; std::istringstream is(input); std::ostringstream os; std::string s; int i; is >> s >> i; os << i << s; std::cout << os.str() << std::endl; } Im obigen Programm wird ein Eingabestream is vom Typ std::istringstream mit dem String input initialisiert. Der Stream is verwendet demnach den String input als Quelle. Es werden dann mit Hilfe des Operators >> Daten formatiert gelesen - zuerst ein String, dann eine Zahl. Diese Daten werden in den Variablen s und i entgegen genommen. Im nächsten Schritt werden die Daten in s und i in umgekehrter Reihenfolge in den Stream os geschrieben. Dieser Stream ist ein Ausgabestream vom Typ std::ostringstream. Alle Daten, die in einen derartigen Stream geschrieben werden, werden intern in einer Variablen vom Typ std::string gespeichert. Die Methode str() gibt diesen internen String zurück. Im obigen Programm wird str() verwendet, um den String zur Kontrolle auf die Standardausgabe auszugeben. Der C++ Standard stellt weitere Stream-Klassen zur Verfügung wie beispielsweise std::ifstream und std::ofstream, die in der Headerdatei fstream definiert sind. Diese Klassen können verwendet werden, um Daten aus Dateien zu lesen bzw. in Dateien zu schreiben. Es existiert außerdem eine Klasse std::fstream, die sowohl von std::istream als auch von std::ostream abgeleitet ist und somit Lese- und Schreiboperationen unterstützt. 7.4 Container. Gruppieren von Daten
  • 6. Ein wichtiger Bestandteil des C++ Standards sind verschiedene Container, die es einfach machen, große Datenmengen zu verwalten. Für größtmögliche Flexibilität sind alle Containerklassen als Templates definiert, so dass sie Daten beliebigen Typs speichern können. So existiert beispielsweise eine Klasse std::vector, die sich grundsätzlich wie ein Array verhält. Der Vorteil dieser Klasse ist jedoch, dass die Größe von Vektoren im Gegensatz zu Arrays dynamisch ist und Elemente beliebig hinzugefügt und entfernt werden können. #include <vector> #include <iostream> int main() { std::vector<char> v; v.push_back('a'); v.push_back('b'); v.push_back('c'); std::cout << v[0] << std::endl; std::cout << v.at(2) << std::endl; std::cout.write(&v[0], v.size()) << std::endl; } Im obigen Programm wird ein Vektor v verwendet, der Elemente vom Typ char speichern kann. Um den Vektor mit drei Buchstaben zu füllen, wird auf die Methode push_back() zugegriffen. Um auf ein Element an einer bestimmten Position im Vektor zuzugreifen, kann entweder der überladene Operator [] oder die Methode at() verwendet werden. Der Unterschied ist: at() überprüft, ob der übergebene Index gültig ist und wirft im Fehlerfall eine Ausnahme vom Typ std::out_of_range. Der Operator [] hingegen überprüft wie bei herkömlichen Arrays nicht, ob der Index innerhalb der gültigen Bandbreite liegt. Vektoren garantieren, dass Elemente direkt hintereinander im Speicher liegen - so wie Sie es von herkömlichen Arrays kennen. Deswegen können wie im obigen Programm geschehen mit Hilfe der Methode write() die Elemente im Vektor als Zeichenkette "abc" auf die Standardausgabe ausgegeben werden. Die Anzahl der Elemente im Vektor wird über die Methode size() ermittelt. Da Vektoren garantieren, dass Elemente direkt hintereinander im Speicher liegen, kann das Hinzufügen eines neuen Elements mit push_back() dazu führen, dass ein Vektor einen neuen Speicherbereich reservieren muss, der für die aktuell gespeicherten Daten und das neu hinzugefügte Elemente groß genug ist. Das bedeutet, dass momentan im Vektor gespeicherte Daten in einen neuen Speicherbereich kopiert werden müssen. Wenn es für Ihr Programm wichtig ist, dass Daten schnell hinzugefügt werden können, ohne das bereits gespeicherte Daten intern an eine andere Position kopiert werden müssen, bietet sich die Liste an. Die entsprechende Klasse heißt std::list und ist in der Headerdatei list definiert. #include <list> #include <iostream>
  • 7. int main() { std::list<char> l; l.push_back('a'); l.push_back('b'); l.push_back('c'); std::cout << l.front() << std::endl; std::cout << l.back() << std::endl; std::cout << l.size() << std::endl; } Der entscheidende Unterschied zwischen einer Liste und einem Vektor ist, dass in einer Liste ein Element mit Hilfe eines Zeigers auf das nächste Element zeigt, während bei einem Vektor alle Elemente direkt hintereinander in einem Speicherbereich liegen. Weil in einer Liste Elemente über Zeiger gekoppelt sind, kann das Hinzufügen eines neuen Elements nicht dazu führen, dass die Liste bereits gespeicherte Daten verschieben muss. Da Entwickler in unterschiedlichen Situationen unterschiedliche Container benötigen, bietet der C++ Standard entsprechend verschiedene Klassen an, aus denen gewählt werden kann. Während der Zugriff auf ein beliebiges Element in einem Array und in einem Vektor über Zeigerarithmetik sehr schnell vonstatten geht, muss sich eine Liste anhand der Zeiger über die Elemente entlang hangeln. Deswegen bietet std::list keinen überladenen Operator [] und keine Methode at() an. Sie müssen sich also selbst anhand der Zeiger vorhangeln, um zu einem Element an einer bestimmten Position in der Liste zu kommen. Mit front() und back() stehen aber zwei Methoden zur Verfügung, um direkt auf das erste und letzte Element in einer Liste zugreifen zu können. Neben std::vector und std::list bietet der C++ Standard weitere Container wie std::deque, std::queue und std::set an. Alle Container sind auf bestimmte Datenstrukturen und Algorithmen zur Verwaltung von Daten spezialisiert, so dass Entwickler den für ihre Situation jeweils besten Container wählen können. Neben den bisher kennengelernten Containern, die einfach nur Gruppen von Daten speichern, gibt es einen Container, in dem jeweils zwei Daten eineinander zugeordnet sind. #include <string> #include <map> #include <utility> #include <iostream> int main() { std::map<std::string, int> m; m.insert(std::make_pair("Anton", 35)); m.insert(std::make_pair("Boris", 31)); m.insert(std::make_pair("Caesar", 40)); std::cout << m["Boris"] << std::endl; } Die Klasse std::map ist ein Template, das zwei Parameter erwartet - nämlich die Typen der Daten, die eineinander zugeordnet werden sollen. Wenn wie im obigen Beispiel Name und
  • 8. Alter von Personen zugeordnet werden sollen, kann std::map mit std::string und int instantiiert werden. In diesem Fall ist der String der Schlüssel und die Zahl der dem Schlüssel zugeordnete Wert. Container vom Typ std::map sortieren Daten anhand der Schlüssel. Dank dieser automatischen Sortierung ist es möglich, Werte über Schlüssel sehr schnell zu finden. So wird im obigen Programm mit Hilfe des überladenen Operators [] nach dem Schlüssel "Boris" gesucht und der dem Schlüssel zugeordnete Wert 31 auf die Standardausgabe ausgegeben. std::map bietet zwar einen Operator [] an. Das heißt aber nicht, dass Daten intern ähnlich wie bei Arrays oder Vektoren gemeinsam in einem Speicherbereich liegen. Im Gegensatz zu Arrays und Vektoren garantieren Container vom Typ std::map noch nicht einmal, dass Daten intern genau in der Reihenfolge gespeichert werden, in der sie dem Container mit insert() hinzugefügt wurden. Damit Container vom Typ std::map Schlüssel schnell finden können, müssen sie Daten sortieren. Sie sollten demnach std::map nicht verwenden, wenn Ihnen die Reihenfolge der Daten, die Sie im Container speichern, wichtig ist und die Reihenfolge unverändert bleiben muss. 7.5 Iteratoren. Zeiger auf Elemente in Containern Alle Container bieten zwei Methoden begin() und end() an, die sogenannte Iteratoren zurückgeben. Es handelt sich dabei um Objekte, mit denen über Elemente in einem Container iteriert werden kann. Der von begin() zurückgegebene Iterator zeigt dabei auf das erste Element, der von end() zurückgegebene Iterator auf die Position hinter dem letzten gültigen Element in einem Container. Iteratoren sind daher etwas ähnliches wie Zeiger. Sie bieten tatsächlich einen überladenen Operator * an, so dass wie bei Zeigern über das Sternchen auf ein Element zugegriffen werden kann, auf das ein Iterator verweist. #include <list> #include <iostream> int main() { std::list<char> l; l.push_back('a'); l.push_back('b'); l.push_back('c'); for (std::list<char>::iterator it = l.begin(); it != l.end(); ++it) std::cout << *it << std::endl; } Im obigen Beispiel wird auf die Liste zugegriffen, die bereits in einem Beispiel im vorherigen Abschnitt verwendet wurde. Mit Hilfe der Iteratoren wird nun über die einzelnen Elemente in der Liste iteriert, um dann über das Sternchen auf das jeweilige Element zuzugreifen und es auf die Standardausgabe auszugeben.
  • 9. Die Schleifenkonstruktion, die Sie im obigen Programm sehen, ist typisch: Mit begin() wird ein Iterator auf das erste Element des Containers erhalten. Bevor über diesen Iterator versucht werden darf, auf ein Element im Container zuzugreifen, wird überprüft, ob er ungleich dem Iterator ist, der von end() zurückgegeben wird. Nach jedem Schleifendurchgang wird der Iterator mit ++ inkrementiert, um ein Element im Container vorzurücken. Der Datentyp des Iterators hängt mit dem Datentyp des Containers zusammen, über dessen Elemente der Iterator laufen soll. Alle Container definieren dazu einen Typ iterator, so dass es nicht notwendig ist, sich neue Klassennamen zu merken. Mit Hilfe der Iteratoren ist es nun möglich, auf jedes beliebige Element in einem Container zuzugreifen. Wenn Sie beispielsweise das zweite Element der Liste ausgeben möchten, können Sie das wie folgt machen. #include <list> #include <utility> #include <iostream> int main() { std::list<char> l; l.push_back('a'); l.push_back('b'); l.push_back('c'); std::list<char>::iterator it = l.begin(); std::advance(it, 1); std::cout << *it << std::endl; } In der Headerdatei utility ist eine Funktion std::advance() definiert, mit der ein Iterator um eine bestimmte Anzahl an Stellen vor- oder zurückgesetzt werden kann. Im obigen Beispiel wird der Iterator, der auf das erste Element im Container zeigt, um eine Stelle vorgerückt und zeigt somit auf das zweite Element im Container. Genausogut hätte in diesem Fall der Iterator natürlich auch mit ++ inkrementiert werden können. Für größere Sprünge ist std::advance() jedoch hilfreich. Der eigentliche Grund, warum es Iteratoren gibt, liegt in ihrer Eigenschaft als Bindeglied zwischen Containern und Algorithmen. So stehen im C++ Standard zahlreiche Algorithmen zur Verfügung, mit denen Daten in Containern verarbeitet werden können. Diese Algorithmen erwarten jedoch als Parameter keine Container, sondern Iteratoren. Auf diese Weise können Algorithmen auf bestimmte Elemente in Containern angewandt werden und müssen nicht sämtliche Elemente in einem Container verarbeiten. 7.6 Algorithmen. Mit Elementen in Containern arbeiten Der C++ Standard definiert gut 60 Algorithmen in der Headerdatei algorithm. Es handelt sich dabei um freistehende Funktionen, die mindestens zwei Iteratoren als Parameter erwarten: Ein Iterator, der auf den Anfang einer Sequenz von Elementen zeigt, und einer, der dahinter zeigt. Wenn Sie einen Algorithmus auf alle Elemente in einem Container
  • 10. anwenden möchten, können Sie als Parameter die Iteratoren übergeben, die von begin() und end() zurückgegeben werden. #include <list> #include <algorithm> #include <iostream> int main() { std::list<char> l; l.push_back('a'); l.push_back('b'); l.push_back('c'); std::reverse(l.begin(), l.end()); for (std::list<char>::iterator it = l.begin(); it != l.end(); ++it) std::cout << *it << std::endl; } Im obigen Beispiel wird der Algorithmus std::reverse() verwendet, der die Reihenfolge der Elemente zwischen den beiden Iteratoren, die als Parameter übergeben werden, umdreht. #include <list> #include <algorithm> #include <iostream> int main() { std::list<char> l; l.push_back('a'); l.push_back('b'); l.push_back('c'); std::list<char>::iterator it = l.begin(); std::advance(it, 1); std::rotate(l.begin(), it, l.end()); for (std::list<char>::iterator it = l.begin(); it != l.end(); ++it) std::cout << *it << std::endl; } Der Algorithmus std::rotate() ist eine Funktion, die als Parameter drei Iteratoren erwartet. std::rotate() rotiert die Elemente, auf die die Iteratoren zeigen, im Kreis. Der mittlere Iterator bestimmt dabei das Element, das nach der Rotation das erste Element sein soll. #include <list> #include <algorithm> #include <iostream> int main() { std::list<char> l; l.push_back('a'); l.push_back('b'); l.push_back('c'); std::cout << std::count(l.begin(), l.end(), 'c') << std::endl; }
  • 11. Mit Hilfe des Algorithmus std::count() kann die Anzahl der Elemente zwischen zwei Iteratoren gefunden werden, die mit dem Wert, der als dritter Parameter übergeben wird, identisch sind. Beachten Sie, dass die Iteratoren, die Sie als Parameter an Algorithmen übergeben, immer auf Elemente im gleichen Container verweisen müssen. Es hat unvorhersehbare Folgen und kann zu einem Absturz führen, wenn Sie Iteratoren übergeben, die auf Elemente in unterschiedlichen Containern verweisen.