Upload
hoanghanh
View
215
Download
0
Embed Size (px)
Citation preview
Zentrum für Angewandte Informatik KölnArbeitsgruppe Faigle / SchraderUniversität zu Köln
15 Standard Template Library (STL)
15.1 Konzept der STL
Die STL ist ein Bestandteil der C++ Standardbibliothek,welche im wesentlichen aus den folgenden Teilen besteht:
1. Generische Datenstrukturen und Algorithmen:
• Container
• Iteratoren
• Algorithmen
2. Internationalisierung:
• Zeichensätze, Datumsformat...
3. Exceptions, Diagnose (s. später)
4. Numerisches:
• komplexe Zahlen
• numerische Felder und zugehörige Operationen
5. Streams (Ein-/Ausgabe)
6. Vermischtes:
• Speicherverwaltung und -zugriff
• Datum und Uhrzeit
• Strings (Zeichenketten)
WS 2001/02 Programmierkurs C++ Seite 236
Zentrum für Angewandte Informatik KölnArbeitsgruppe Faigle / SchraderUniversität zu Köln
Die STL beinhaltet im wesentlichen den Punkt 1.) derAufzählung. Ihr Schwerpunkt liegt auf dabei aufDatenstrukturen undAlgorithmen und nutzt denTemplate–Mechanismuszur Parametrisierung vonKomponenten (Generizität).
Hinter dem Gedanken der Generizität steckt die folgendeIdee:
Kann ein Algorithmus unabhängig vonRepräsentationsdetails ausgedrückt werden und ist dieserschwinglich erreichbar (d.h. ohne logische Verrenkungen),so sollte man dies tun.
Beispiel:
Jemand, der eine Stack benötigt, braucht nicht unbedingteinen Stack für Zeichen. Ein Stack ist ein allgemeinesKonzept, das unabhängig von dem Begriff Zeichen existiert.Folgerichtig sollte ein Stack deshalb auch unabhängig davonrepräsentiert werden.
Zum Konzept der STL gleich mehr, zunächst eine Übersichtüber ihre wichtigsten Elemente:
Container „Behälter“ für Objekte bzw. Objekte, die zurVerwaltung anderer Objekte dienen (auchbenutzer-definierte Datentypen). Objekte können per Wert(lvalue) oder per Referenz abgelegt werden.
WS 2001/02 Programmierkurs C++ Seite 237
Zentrum für Angewandte Informatik KölnArbeitsgruppe Faigle / SchraderUniversität zu Köln
Iteratoren Iteratoren arbeiten wie Zeiger. Je nach
Anwendungsfall können sie selbst gewöhnliche Zeiger
oder Objekte mit zeigerähnlichen Eigenschaften sein.
Iteratoren bewegen sich über die Elemente eines
Containers und stellen den Zugriff auf diese Elemente
bereit.
Algorithmen Die Template–Algorithmen arbeiten mit
Iteratoren, die auf Container zugreifen. Damit diese
sowohl benutzerdefinierte Datentypen unterstützen als
auch die in C++ vorhandenen Datentypen, sind die
Algorithmen so entworfen, daß sie ebenso gut mit
normalen Zeigern arbeiten.
Die Iteratoren entkoppeln also Container und Algorithmen:
Letztere sind als Template implementiert, die über den Typ
des Iterators parametrisiert sind. Damit ist ihre Nutzung nicht
auf einen einzelnen speziellen Typ von Container beschränkt.
Dieser Ansatz der generischen Programmierung mit Hilfe
von Templates bietet gegenüber einem Ansatz mittels
Vererbung und Polymorphismus den großen Vorteil, daß die
Anzahl der notwendigen verschiedenen Container- und
Algorithmentypen drastisch reduziert wird:
WS 2001/02 Programmierkurs C++ Seite 238
Zentrum für Angewandte Informatik KölnArbeitsgruppe Faigle / SchraderUniversität zu Köln
Ein Element des Datentypsint soll in einem Container
vector mit Hilfe eines Algorithmusfind() gefunden
werden.
• Fürn Containertypen brauchen wir für jeden Container
einen eigenen Algorithmusfind() .
• Soll das Ganze für einen beliebigen vonm Datentypen
funktionieren, steigt die Anzahl derfind()
Algorithmen aufm× n.
• Betrachtet mank Algorithmen, wäre die
Implementierung vonm× n× k Algorithmen
erforderlich.
• Die Verwendung von Templates erlaubt es, die Anzahlm
auf1 zu reduzieren.
• Durch die Entkopplung von Containern und Algorithmen
über die Iteratoren reduziert sich die Anzahl zu
implementierender Algorithmen aufn+ k.
Da Templates bereits zur Compilationszeit ausgewertet
werden, besitzt dieses Vorgehen darüber hinaus den Vorteil
derTypsicherheit.
WS 2001/02 Programmierkurs C++ Seite 239
Zentrum für Angewandte Informatik KölnArbeitsgruppe Faigle / SchraderUniversität zu Köln
Nebenbemerkungen:
• Die STL stellt einen Rahmen zur Verfügung, den
Programmierer relativ leicht um eigene Komponenten
erweitern können.
• Die STL ist mit dem Ziel hoher Effizienz entworfen. Je
nach Art und Größe des zu bearbeitenden Containers
kann die Laufzeit eines Algorithmus stark variieren (s.a
15.2).
• Die zur STL gehörenden Algorithmen sind nicht nur auf
STL–Container sondern auch auf einfache Felder
anwendbar (s.a. 15.3).
• Durch die Aufnahme der STL in die C++
Standardbibliothek führt ihre Verwendung anstelle von
„selbst–gestrickten“ Bibliotheken dazu, daß die
Programme
– leichter portierbar (Compiler–Hersteller orientieren
sich an Standards)
– und leichter wartbar (verbreitetes Know-how)
sind.
WS 2001/02 Programmierkurs C++ Seite 240
Zentrum für Angewandte Informatik KölnArbeitsgruppe Faigle / SchraderUniversität zu Köln
15.2 Beispiele aus der STL
Die Anzahl der Container der STL und ihrer Methoden läßt
sich hier nicht komplett darstellen. Eine gute Übersicht findet
sich unter
http://www.sgi.com/tech/stl/
Hier werden nur einige Container–Klassen präsentiert, im
folgenden Abschnitt 15.3 das Zusammenspiel zwischen den
einzelnen STL–Komponenten näher beleuchtet.
Containerklassevector
Die Container–Klassevector<T> (T steht für einen
Datentyp) ist eine Sequenz von Elementen, die einem Feld
von Daten sehr ähnlich ist. Sie ist die einfachste der
STL–Container–Klassen.
Der Standard schreibt für Container keine spezielle interne
Darstellung vor, sondern Schnittstellen und Anforderungen
zur Komplexität. Fürvector ist die Datenstruktur der
Elemente einem Feld angenähert:
GroesseDarstellung
vector:Elemente extra Platz
WS 2001/02 Programmierkurs C++ Seite 241
Zentrum für Angewandte Informatik KölnArbeitsgruppe Faigle / SchraderUniversität zu Köln
Eigenschaften:
• leichter und effizienter Zugriff auf einzelne Elemente in
beliebiger Reihenfolge (random access iterator).
• Einfügen und Entfernen von Elementen am Vektorende
in konstanter Zeit.
• Einfügen und Entfernen von Elementen an anderen
Positionen in linearer Zeit.
• Zahl der Elemente dynamisch variierbar. Das
Speichermanagment wird automatisch vom Container
übernommen (letzteres gilt für alle Container–Klassen).
Beispiel:
Wir wollen eine Liste von Fenstern verwalten.
#include <vector>
using namespace std;
typedef int window;
int main()
{
vector<window> msw;
}
WS 2001/02 Programmierkurs C++ Seite 242
Zentrum für Angewandte Informatik KölnArbeitsgruppe Faigle / SchraderUniversität zu Köln
Die Typvereinbarung dient lediglich dazu, die Klasse
window nicht definieren zu müssen, im Prinzip darf hier
jeder Datentyp stehen.
Bei der Vereinbarung des Containersmswwird der
Default–Konstruktor der Template–Klassevector
aufgerufen.
Die Methode
vector<T>::size_type max_size()
liefert die maximale Anzahl von Elementen, die im
Vektor des DatentypsT gespeichert werden kann. Diese
ist system–abhängig.
vector<T>::size_type capacity()
liefert die Anzahl der Elemente, für die Speicher
allokiert wurde.
cout << msw.capacity() << endl;
$>> 0
Bemerkung:Die im folgenden aufgeführten Methoden sind
auch für die anderen STL-Container vorhanden
$>> soll im folgenden immer die Ausgabe des Vektors
bedeuten.
WS 2001/02 Programmierkurs C++ Seite 243
Zentrum für Angewandte Informatik KölnArbeitsgruppe Faigle / SchraderUniversität zu Köln
Es läßt sich auch ein Vektor vereinbaren, der Speicher füreine anzugebende Zahl von Elementen allokiert:
vector<window> msw2(10);
Der Zugriff auf Elemente des Vektors kann dann mittels desOperators[index] erfolgen (nur für Container mitrandom
access).
Aber Achtung: Der Zugriff über den Operator geschiehtohne Überprüfung der Gültigkeit des Index:
msw2[0] = 2; // erlaubt
msw2[9] = 5; // erlaubt
msw[0] = 2; // compiliert,
// aber Programmabsturz
Der Vorteil der STL-Container ist aber gerade, daß diese sichselbst um die Allokation von Speicher kümmern. Deshalbgibt es Methoden, die ein Einfügen von Elementen auchermöglichen, wenn der Container vorher keinen Speicherallokiert hat.
Dervector ist insbesondere effizient, wenn Elemente ansEnde eingefügt werden
msw.push_back(17);
msw.push_back(42);
cout<<"("<< msw[0] <<","<< msw[1] <<")"<<endl;
$>> msw = (17,42)
WS 2001/02 Programmierkurs C++ Seite 244
Zentrum für Angewandte Informatik KölnArbeitsgruppe Faigle / SchraderUniversität zu Köln
Einfügen kann man natürlich auch an anderen Positionen:
iterator insert(iterator pos, const T& x)
Einfügen eines Elements vom DatentypT vor der
Position, auf die der Iteratorpos zeigt.
Iteratoren sollen bis auf wenige Bemerkungen im weiteren
nicht näher behandelt werden.
Sequenz–Container besitzten einige ausgezeichnete
Iteratoren:
17 42msw
begin() end()
iterator begin()
Liefert einen Iterator, der auf den Beginn des Vektors
zeigt.
iterator end()
Liefert einen Iterator, der auf das Ende des Vektors zeigt.
Ende bedeutet hier hinter das letzte Element des
Containers.
msw.insert(msw.begin(),100);
msw.insert(msw.end(),3);
$>> msw = (100,17,42,3)
WS 2001/02 Programmierkurs C++ Seite 245
Zentrum für Angewandte Informatik KölnArbeitsgruppe Faigle / SchraderUniversität zu Köln
Iteratoren verhalten sich ähnlich wie Zeiger
vector<window>::iterator first = msw.begin();
vector<window>::iterator last = msw.end();
while(first != last){
cout << *first;
first++;
}
$>> 100 17 42 3
• Wie ein Zeiger besitzt ein Iterator den
Dereferenzierungsoperatoroperator* . Er liefert den
Wert des Datentypes, der im Container verwaltet wird.
• Wie ein Zeiger kann ein Iterator mit dem Operator
operator++ inkrementiert werden (genaues vom
Iteratortyp abhängig).
Es wurde schon gesagt, daß über die Iteratoren Algorithmen
und Container entkoppelt werden, z.B. gibt es die
Möglichkeit, den Container zu sortieren. Der Bereich, der
sortiert werden soll, wird durch zwei Iteratoren
gekennzeichnet:
sort(msw.begin(),msw.end());
$>> msw = (3,17,42,100)
(algorithm einbinden, umsort() verwenden zu können.)
WS 2001/02 Programmierkurs C++ Seite 246
Zentrum für Angewandte Informatik KölnArbeitsgruppe Faigle / SchraderUniversität zu Köln
Weitere Konstruktoren und Zuweisung:
vector(size_type n, const T& t)
Erzeugt einen Vektor mitn Kopien der Datenstrukturt .
vector(const vector&)
Copy–Konstruktor
vector& operator=(const vector&)
Zuweisungsoperator
vector(InputIterator, InputIterator)
Erzeugt einen Vektor aus dem Wertebereich einesanderen Vektors
vector<window> v1 (3,17)
vector<window> v2 (msw);
vector<window> v3 = v2;
$>> v1 = (17,17,17) v2 = v3 = (3,17.42,1)
vector<window> v4 (msw.begin(),msw.end()-2);
$>> v4 = (3,17)
AuchVergleichsoperatorenexistieren. So lassen sich zweiVektoren über== auf Gleichheit überprüfen,< führt einenlexikographischen Vergleich durch (d.h. zuerst werden dieersten Elemente verglichen, bei Gleichheit die zweiten usw.).
Die übrigen Vergleichsoperatoren lassen sich aus diesenableiten (using namespace std::rel_ops ), z.B.
WS 2001/02 Programmierkurs C++ Seite 247
Zentrum für Angewandte Informatik KölnArbeitsgruppe Faigle / SchraderUniversität zu Köln
template <class T>
inline bool operator != (const T& x, const T& y)
{
return !(x == y);
}
Weitere Methoden:
reference front()
Liefert das erste Element des Vektors
cout << msw.front() << endl;
$>> 3
reference back()
Liefert das letzte Element des Vektors
void pop_back()
Entfernt das letzte Element des Vektors
void clear()
Entfernt alle Elemente des Vektors
bool empty() const
Liefert true , wennsize() == 0
insert(iterator p, size_type n, const T& x)
Einfügen vonn Kopien vonx vor die Position des
Iteratorsp
WS 2001/02 Programmierkurs C++ Seite 248
Zentrum für Angewandte Informatik KölnArbeitsgruppe Faigle / SchraderUniversität zu Köln
insert(iterator pos, InputIterator f,
InputIterator l)
Einfügen eines Bereiches[f...l] vor die Position
pos
erase(iterator f, iterator l)
Entfernen des Bereiches[f...l]
Achtung, Iteratoren können ungültig werden!
Containerklasselist
Darstellunglist
Eigenschaften:
• Sequenzklasse, die für das Einfügen und Löschen von
Elementen optimiert ist.
• Iteratoren bleiben dabei gültig.
• aus Effizienzgründen kein Index–Zugriff.
• vor– / rückwärts durchlaufbar (bidirectional iterator).
WS 2001/02 Programmierkurs C++ Seite 249
Zentrum für Angewandte Informatik KölnArbeitsgruppe Faigle / SchraderUniversität zu Köln
Containerklassedeque
Queue mit zwei Enden (double–ended queue). Diese istdahingehend optimiert, daß die Operationen an den beidenEnden so effizient wie bei Listen sind und der Index–Zugriffsich der Effizienz eines Vektors annähert.
Effizienz des Einfügens / Löschens:
Container Anfang mittendrin Ende
Vector linear linear konstant∗
List konstant konstant konstant
Deque konstant∗ linear konstant∗
∗ kann linear werden, wenn wegen Reallokation von Speicher
Elemente kopiert werden
Container Operation Iterator
vector insert Reallokation notwendig
alle Iteratoren ungültig
keine Reallokation – alle Iteratoren
vor Einfügeposition gültig
erase alle Iteratoren ab Position
an der gelöscht wird, ungültig
WS 2001/02 Programmierkurs C++ Seite 250
Zentrum für Angewandte Informatik KölnArbeitsgruppe Faigle / SchraderUniversität zu Köln
Container Operation Iterator
list insert alle Iteratoren bleiben gültig
erase nur Iteratoren auf das gelöschte
Element werden ungültig
deque insert alle Iteratoren werden ungültig
erase alle Iteratoren werden ungültig
15.3 Zusammenwirken derSTL–Komponenten
Mit Hilfe eines einfachen Programmes in verschiedenen
Varianten, soll das Wirken der STL–Komponenten
demonstriert werden:
In dem Programm soll ein per Dialog einzugebender
int –Wert in einem Array gefunden werden, wozu eine
Funktionfind() benutzt wird, der auch als Algorithmus
der STL vorliegt.
Die Beispiele wurden aus „Komponenten entwerfen mit der C++
STL“ von Ulrich Breymann, Addison–Wesley entnommen.
WS 2001/02 Programmierkurs C++ Seite 251
Zentrum für Angewandte Informatik KölnArbeitsgruppe Faigle / SchraderUniversität zu Köln
Ohne STL:
int main() {
const int Count = 100;
int aContainer[Count]; // define container
// pointer to the beginning
Iterator begin = aContainer;
// position after the last element
Iterator end = aContainer + Count;
// fill container with even numbers
for(int i = 0; i < Count; ++i)
aContainer[i] = 2*i;
int Number = 0;
while(Number != -1) {
cout << " enter required number (-1 = end):";
cin >> Number;
if(Number == -1) break;
Iterator pos = find(begin,end,Number);
if (pos != end)
cout << "found at position "
<< (pos - begin) << endl;
else
cout << Number << " not found!" << endl;
}}}
WS 2001/02 Programmierkurs C++ Seite 252
Zentrum für Angewandte Informatik KölnArbeitsgruppe Faigle / SchraderUniversität zu Köln
Der TypnameIterator ist hier als Zeiger aufint
vereinbart und soll die Ähnlichkeit von Zeigern und
Iteratoren ausdrücken:
typedef int* Iterator;
Der Algorithmusfind ist wie folgt implementiert:
Iterator find(Iterator begin,
Iterator end,
const int& Value) {
// pointer comparison &&
// dereferencing and object comparison
while(begin != end
&& *begin != Value)
++begin;
return begin;
}
• find() muß nichts über den Container wissen
• Der Iterator (Zeiger) benötigt einige wenige Fähigkeiten:
– Inkrement++
– Dereferenzierung*
– Vergleich über!=
WS 2001/02 Programmierkurs C++ Seite 253
Zentrum für Angewandte Informatik KölnArbeitsgruppe Faigle / SchraderUniversität zu Köln
Algorithmus find() als Template:
template<class Iteratortype, class T>
Iteratortype find(Iteratortype begin,
Iteratortype end,
const T& Value) {
// iterator comparison
// dereferencing and object comparison
while(begin != end && *begin != Value)
++begin;
return begin;
}
! Programm weiter lauffähig
! Der Algorithmus kann mit jeder Klasse von Iteratoren
zusammenarbeiten, die die Operationen!= und* zur
Verfügung stellen.
STL-Container vector:
Ersetze den Container durch einen Container der STL. DieIteratorenbegin undend werden durch die Methoden derKlassevector<T> ersetzt, die einen entsprechendenIterator zurückgeben.
#include <vector>
using namespace std;
typdef vector<int>::iterator Iterator;
WS 2001/02 Programmierkurs C++ Seite 254
Zentrum für Angewandte Informatik KölnArbeitsgruppe Faigle / SchraderUniversität zu Köln
int main() {
const int Count = 100;
vector<int> aContainer(Count); //!!!
for(int i = 0; i < Count; ++i)
aContainer[i] = 2*i;
int Number = 0;
while(Number != -1) {
cin >> Number;
if(Number == -1) break;
// globale Funktion find() benutzen
Iterator pos = ::find(aContainer.begin(),
aContainer.end(), Number);
if (position != aContainer.end())
cout << "found at position "
<< (position - aContainer.begin());
else cout << Number << " not found!";
}}
• STL-Container arbeitet mit unseremfind() zusammen
• Mit Iteratoren ist Arithmetik möglich (Differenzbildung)
komplett mit STL
• Ersetze::find() durchfind() (d.h. Namespacestd ).
• Ergänze#include<algorithm> .
• Die typedef Anweisung ist nicht mehr notwendig
WS 2001/02 Programmierkurs C++ Seite 255
Zentrum für Angewandte Informatik KölnArbeitsgruppe Faigle / SchraderUniversität zu Köln
15.4 Interne Funktionsweise
• Verwendung einer selbstgeschriebenen Klasseslist ,
die sich wie ein Container der STL verhält.
• Die Klasse wird nicht vollständig definiert.
• Laufzeitverhalten unerheblich.
int main() {
const int count = 100;
slist<int> aContainer; // Container
// Container "von vorne" fuellen
for(int i = count; i >= 0; --i)
aContainer.push_front(2*i);
int Number = 0;
while(Number != -1) {
cin >> Number;
if (Number == -1) break;
slist<int>::iterator Position =
find(aContainer.begin(),aContainer.end(),
Number);
if(Position != aContainer.end())
cout << "found at position "
<< (Position - aContainer.begin());
else cout << "not found\n";
}}
WS 2001/02 Programmierkurs C++ Seite 256
Zentrum für Angewandte Informatik KölnArbeitsgruppe Faigle / SchraderUniversität zu Köln
Defintion von slist
template<class T>
class slist {
public:
slist() : firstElement(0), Count(0) {}
void push_front(const T& Datum) {
firstElement =
new ListElement(Datum, firstElement);
++Count;}
private:
struct ListElement {
T Data;
ListElement *Next;
ListElement(const T& Datum,ListElement* p)
: Data(Datum), Next(p) {}
};
ListElement *firstElement;
size_t Count;
• push_front() erzeugt ein neues Listenelement undfügt es am Anfang der Liste ein.
• Listenelemente als geschachtelte öffentliche Klassestruct definiert. Zugriff von außerhalb nicht möglich.
• Elemente als einfach verkette Liste vorgehalten (keinOperator[] .
WS 2001/02 Programmierkurs C++ Seite 257
Zentrum für Angewandte Informatik KölnArbeitsgruppe Faigle / SchraderUniversität zu Köln
slist stellt Iterator zur Vefügung
public:
class iterator {
public:
iterator(ListElement* Init = 0)
: current(Init){}
// Dereferenzierung
T& operator*() {
return current->Data;}
const T& operator*() const {
return current->Data;}
// Praefix
iterator& operator++() {
if(current) // Ende erreicht?
current = current->Next;
return *this;}
// Postfix
iterator operator++(int) {
iterator temp = *this;
++*this;
return temp;}
Bemerkung:Innerhalb der Postfix-Variante des++–Operators
wird der Kopierkonstruktor benötigt. Die Präfix–Variante
sollte deshalb bevorzugt werden.
WS 2001/02 Programmierkurs C++ Seite 258
Zentrum für Angewandte Informatik KölnArbeitsgruppe Faigle / SchraderUniversität zu Köln
bool operator==(const iterator& x) const {
return current == x.current;}
bool operator!=(const iterator& x) const {
return current != x.current;}
// Differenzoperator
int operator-(Iterator fromWhere){
int count = 0;
while(fromWhere.current != current
&& fromWhere.current != 0){
++count;
++fromWhere;}
return count;
}
private:
// Zeiger auf aktuelles Element
ListElement* current;
}; // class iterator
Bemerkung:Die Differenz zwischen zwei Iteratoren ist durchdie Anzahl der Inkrementierungen gegeben, die benötigtwerden, um vom ersten Iterator zum zweiten zu gelangen.
public:
iterator begin() const
{ return iterator(firstElement);}
iterator end() const { return iterator();}
WS 2001/02 Programmierkurs C++ Seite 259