JLI Spieleprogrammierung Foren-Übersicht JLI Spieleprogrammierung

 
 FAQFAQ   SuchenSuchen   MitgliederlisteMitgliederliste   BenutzergruppenBenutzergruppen 
 medals.php?sid=25dfac6085991350707419b2ec1353b2Medaillen   RegistrierenRegistrieren   ProfilProfil   Einloggen, um private Nachrichten zu lesenEinloggen, um private Nachrichten zu lesen   LoginLogin 

[C++/STL] Teil 1: Sequentielle Container

 
Neues Thema eröffnen   Neue Antwort erstellen    JLI Spieleprogrammierung Foren-Übersicht -> Tutorials
Vorheriges Thema anzeigen :: Nächstes Thema anzeigen  
Autor Nachricht
Dragon
Super JLI'ler


Alter: 37
Anmeldedatum: 24.05.2004
Beiträge: 340
Wohnort: Sachsen
Medaillen: Keine

BeitragVerfasst am: 20.08.2005, 21:53    Titel: [C++/STL] Teil 1: Sequentielle Container Antworten mit Zitat

Einführung
In diesem Tutorial möchte ich Ihnen die Standard Template Libary (kurz STL) etwas näher bringen. Die STL wurde ursprünglich von HP entwickelt und gehört nun zum Sprachstandard von C++. Die Grundlage dieser Bibliothek sind Templates. Jeder sollte schon mal selber ein paar Template-Klassen geschrieben haben um die STL besser zu verstehen. Alle Klassendefinitionen stehen im Namensbereich std. Ich werde den Namen des Namensbereich immer vor die Klasse schreiben, wer will kann auch „using namespace std;“ vor die main schreiben und brauch das zusätzliche davor schreiben des Namensbreich nicht machen. Alle Headerdateien der STL besitzen kein h als Dateiendung. In diesem Tutorial geht er hauptsächlich um Containerklassen. Ich werde Ihnen zeigen wie man diese benutzt.
Es gibt eine Unterscheidung der Containerklassen. Es gibt einmal die Datencontainer und Container-Adapter die einen Container in sich kapseln (zB. stack). Desweiteren unterscheidet man die Datencontainer in Sequentielle Container und in Assoziative Container.
Folgende Containerklassen gibt es:
Datencontainer
Sequentielle Container
->vector
->deque
->list
Assoziative
->set / multiset
->map / mutlimap
Container-Adapter
->stack
->queue

Allgemeines
Es gibt einige Methoden die alle Container haben. Die Anzahl der Elemente in einem Container kann man mit der Methode size() abfragen.
CPP:
std::cout << "Anzahl der Elemente: " << container.size() << std::endl;

Mit der Methode empty() bekommen wir einen booleschen Wert zurück der uns sagt, ob der Container leer ist oder nicht.
CPP:
std::cout << ( container.empty() ? "Container ist leer" : "Container hat Elemente") << std::endl;


Soll der Container komplett geleert werden, dann nutzt wir die Methode clear()
CPP:
container.clear();



Sequentielle Container
Die Liste (std::list)
Eine std::list ist eine Datencontainer der im Hintergrund eine doppelt verkettete Liste enthält. Jeder hat bestimmt schon mal eine solche Kette programmiert, wenn nicht, dann sollte es eine schöne Übung sein Wink. Um Instanzen dieser Containerklasse erzeugen zu können, müssen wir die Headerdatei <list> einfügen.
CPP:
#include <list>


Als erstes legen wir eine std::list mit unserem Datentyp an. Es können auch Klassen genommen aber zur Demonstration nehme ich hier den Standarddatentyp int. Unsere Liste erhält den Namen "zahlen".
CPP:
std::list<int> zahlen;


Für das hinzufügen von Datensätzen gibt es zwei Möglichkeiten. Wir können unsere Datensätze an den Anfang oder ans Ende setzen. Um einen Datensatz an den Anfang der Liste einzufügen nehmen wir die Methode "push_front()". Soll der Datensatz aber an das Ende eingefügt werden, so nehmen wir die Methode "push_back()". In diesem Beispiel fügen wir 3 Zahlen an das Ende und 3 Zahlen an den Anfang.
CPP:
zahlen.push_back(911);
zahlen.push_back(4711);
zahlen.push_back(12345);
zahlen.push_front(-2000);
zahlen.push_front(2005);
zahlen.push_front(306);

Genau so, wie wir die Daten an den Anfang und ans Ende hinzugefügt haben, können wir sie auch wieder löschen. Dafür gibt es die Methoden pop_front() und pop_back();
CPP:
zahlen.pop_front();   //löscht das erste Element in der Liste
zahlen.pop_back();   //löscht das letze Element in der Liste

Die Anzahl der Einträge der Liste bekommen wir mit der Methode size() heraus.
CPP:
std::cout << "Anzahl der Einträge: " << zahlen.size() << std::endl;

Jetzt wollen wir aber auch wissen, was in der Liste drin steht. Um das erste und das letzte Element anzuzeigen gibt es die Methode "front()" bzw. "back()". Diese geben jeweils eine Referenz zurück.
CPP:
std::cout << "Das erste Element: " << zahlen.front() << std::endl;
std::cout << "Das letzte Element: " << zahlen.back() << std::endl;

Um nun alle Elemente anzuzeigen, müssen wir einen Iterator für unsere Liste erzeugen.
CPP:
//   Iterator erzeugen
std::list<int>::iterator it;

//   Die Liste durchgehen
for(it=zahlen.begin(); it!= zahlen.end(); it++)
{
   std::cout << *it << std::endl;
}

//   Es ist auch möglich den Wert des Iterators zu verändern
for(it=zahlen.begin(); it!= zahlen.end(); it++)
   (*it) += 100;

Das Löschen eines Elements ist eine komplizierte Angelegenheit. Ach Quatsch, das ist genau so leicht wie der Rest Wink. Es gibt aber etwas zu beachten. Wenn wir ein Element löschen dann zeigt unser Iterator noch auf das gelöschte.
Zum Glück gibt es eine Methode zum löschen die uns dann einen neuen gültigen Iterator zurück gibt. Zum löschen oder besser gesagt zum entfernen eines Elements aus einem Container, wird uns die Methode "erase()" zur Verfügung gestellt.
In dem folgendem Beispiel werden alle Zahlen gelöscht die kleiner als 1000 sind. Um zu demonstrieren wie der Iterator reagiert wird der Wert vor dem löschen und nach dem löschen ausgegeben.
CPP:
for(it = zahlen.begin(); it!= zahlen.end(); it++)
{
   if(*it < 1000)
   {
      std::cout  << "Vor dem loeschen: " << *it << std::endl;
      it = zahlen.erase(it);
      std::cout << "Nach dem loeschen: " << *it << std::endl;
   }
}

Beispiel mit einer Klasse
Ich möchte Ihnen nun ein Beispiel zeigen, wie man eine Liste mit Objekten füllt und sie ausgibt. Hierzu nehme ich eine kleine Klasse die den Namen und das Geburtsjahr speichert. Mit dem Konstruktor oder der set()-Methode können die Werte geändert werden und mit den beiden get-Methoden bekommen wir die Werte zurück.
CPP:
class Person
{
public:
   Person(const char* n = NULL, int j = 1950) : gebjahr(j)
   {
      if(n) strcpy(name, n);
   }

   void set(const char* n, int j)
   {
      gebjahr = j;
      strcpy(name, n);
   }

   int getGebJahr(){return gebjahr;};
   const char* getName(){return name;};

private:
   char name[128];
   int gebjahr;[b]
};

void testprogramm()
{
   //   Eine Liste von Personen erzeugen
   std::list<Person> personen;

   //   Das hinzufügen von Objekten über den Konstruktor aufruf
   personen.push_back(Person("Sven Burow", 1986));
   personen.push_back(Person("Max Mustermann", 1900));

   //   Wir können auch eine Instanz der Klasse erzeugen und diese dann
   //   hinzufügen. Dabei werden die Werte aber nur kopiert und in der
   //   Liste entsteht eine neue Instanz.

   Person person1("Das Nek", 1960);
   personen.push_back(person1);

   Person person2;
   person2.set("Bart Simpson", 1988);
   personen.push_back(person2);

   std::list<Person>::iterator it;
   for(it = personen.begin(); it != personen.end(); ++it)
   {
      std::cout << "Name: " << (*it).getName() << " Geburtsjahr:" <<(*it).getGebJahr() << std::endl;
   }
}

[b]Vector

Ein dynamisches Feld kann zur Laufzeit seine Größe ändern. Wie ein normales Feld können die Datensätze über die eckigen Klammern angesprochen werden. Der std::vector hat eine Besonderheit. Die Datensätze liegen im Speicher direkt hintereinander. Zum Beispiel können wir ganz leicht alle Datensätze in eine Binärdatei schreiben ohne sie einzeln durchgehen zu müssen.
Als erstes müssen wir die Headerdatei <vector> einfügen.
CPP:
#include <vector>

Zur Demonstration werde ich hier auch wieder nur ein Vector mit Datensätzen vom Typ int anlegen. Es sollte aber kein Problem sein, dies auch für Klassen umzusetzen.
CPP:
std::vector<int> zahlen(10, 2005);


Mit dieser Zeile legen wir ein Vector mit 10 Elementen an, die alle als Standardwert 2005 haben. Lassen wir den Konstruktoraufruf weg, so enthält unser Vektor keine Elemente. Wie ein normales Feld können wir nun die Werte verändern.
CPP:
zahlen[0] = 911;
zahlen[1] = 4711;
zahlen[2] = 3141;

Wenn wir nur das letzte und das erste Element ausgeben wollen bieten uns die Methoden „front()“ und „back()“ an. „front()“ gibt eine Referenz auf das erste Element und „back()“ auf das Letzte zurück.
CPP:
std::cout << "erste Element: " << zahlen.front() << std::endl;
std::cout << "letzes Element: " << zahlen.back() << std::endl;

Kommen wir nun zur Ausgabe aller Werte. Das einzigste was wir brauchen ist die Größe des Vektors. Diese bekommen wir über die Methode „size()“ heraus. In einer for-Schleife können wir alle Elemente durchgehen und ausgeben, so wie man es von einem Normalen Feld gewöhnt ist.
CPP:
for(int i = 0; i < zahlen.size();  ++i)
{
   std::cout << "zahlen[" << i << "]=" << zahlen[i] << std::endl;
}

Wenn sie dies nun ausführen werden Sie merken, das 10 Datensätze ausgegeben werden. Die ersten drei so wie wir sie geändert haben und die Letzten sieben mit den Standardwert 2005.
Für das Ändern der Größe können wir die Methode resize() benutzen. Wir übergeben ihr die neue Größe und wenn nötig den neuen Standardwert. Wenn wir das Feld verkleinern, dann werden alle Werte die wegfallen gelöscht. Wird das Feld vergrößert, so werden die neuen Werte mit dem Standardwert initialisiert. Doch was passiert, wenn kein Standardwert angegeben wurde? Die Werte der Felder sind nicht definiert.
Es gibt noch eine andere Möglichkeit Werte dem Vektor hinzuzufügen. Mit push_back() wird ein neuer Wert angehängt dann wird der Vektor automatisch mit vergrößert. Folgendes Beispiel sollte es gut zeigen.
CPP:
void bsp1()
{
   //   Unser Vektor soll 10 Elemente mit den Wert 2005 bekommen
   std::vector<int> zahlen(10, 2005);

   //   nun fügen wir noch 2 hinten dran
   zahlen.push_back(1986);
   zahlen.push_back(2002);

   //   Größe ausgeben
   std::cout << "Groesse des Vektors: " << zahlen.size() << std::endl;

   //   Den kompletten Vektor ausgeben
   for(int i=0; i<zahlen.size(); ++i)
   {
      std::cout << "zahlen[" << i << "]=" << zahlen[i] << std::endl;
   }
}

Unser Vektor hat nun eine Größe von 12 Elementen und die letzen beiden Werte sind 1986 und 2002. Genau so leicht wie wir jetzt Werte hinzugefügt haben, können wir sie auch wieder entfernen. Mit pop_back() entfernen wir das letzte Element.
zahlen.pop_back();
Es ist auch das durchlaufen der Werte mit Iteratoren möglich. Zu erst muss ein Iterator erzeugt werden.
CPP:
//   Iterator erzeugen
std::vector<int>::iterator it;

//   von Anfang bis Ende alles durchgehen ..
for(it = zahlen.begin(); it != zahlen.end(); it++)
{
   //   und ausgeben
   std::cout << *it << std::endl;
}


Deque
Ein Vektor hat einen Nachteil, er kann nur von hinten erweitert werden. Es gibt keine Möglichkeit an den Anfang einen Wert hinzuzufügen. Möchten Sie einen Vektor der genau diese Eigenschaft besitzt, dann müssen sie den Datencontainer deque nehmen. Es ist ein dynamisches Feld, das nach beiden Seiten wächst. Dieser bietet uns zwei zusätzliche Methoden. Einmal push_front() und pop_front(). Wie bei dem Vektor können wir mit dem []-Operator auf die Elemente zugreifen.
Das kleine Beispiel sollte alles erklären da es im Prinzip „nur“ eine Erweiterung des std::vector ist.
CPP:
//   Einen Datencontainer anlegen
std::deque<int> zahlen(5, 2005);

//   das erste Element entfernen
zahlen.pop_front();   

//   Zahlen an das Ende und an den Anfang einfügen
zahlen.push_back(70999);
zahlen.push_front(1986);   

//   Alle Zahlen ausgeben
for(int i=0; i<zahlen.size(); i++)
{
   std::cout << zahlen[i] << std::endl;
}

Iteratoren
Iteratoren sind Containerunabhänging. Sie werden benötigt um die Elemente eines Containers durchzugehen. Mit ihnen kommt man an die Werte des Containers. Ein Iterator wird bei jedem Container gleich erzeugt. Mit den Operator ++ kommen wir zum nächsten Element eines Containers. Um dann den Iterator durch einen Container wandern zu lassen müssen wir ihn als Startwert den Beginn des Containers geben. Dafür gibt es die Methode "begin()". Diese zeigt auf das erste Element. Um zu erfahren wann den z.B. die Liste zu Ende ist, gibt es die Methode "end()". Sie zeigt hinter das letzte Element. Ein Iterator kann man auf Gleichheit (==) oder Ungleichheit(!=) prüfen. Iteratoren lassen sich derefernzieren, d.h. mit dem *-Operator können wir auf den Wert zugreifen. Es gibt kein größer oder kleiner als, darum muss die Laufbedingung einer Schleife !=end() sein. Hier ein Beispiel mit einer Liste.
CPP:
//   Einen Container erzeugen, der Zahlen speichert
std::list<int> container;

//   einen Iterator für unseren Container erzeugen
std::list<int>::iterator it;

//Alle werte im Container ausgeben
for(it = container.begin(); it != container.end(); ++it)
{
   std::cout << *it << std::endl;
}

Wollen wir konstante Iteratoren, sprich wir wollen den Wert nicht verändern dürfen, so müssen wir einen const_iterator anlegen.
CPP:
std::list<int>::const_iterator it;

for(it=container.begin(); it!= container.end(); it++)
{
   std::cout << *it << std::endl;
   (*it)++;   //   hier meldet der Compiler einen Fehler da Konstanten
         //   nicht geändert werden dürfen
}

Ich werde Ihnen nun zeige wie man eine Liste rückwärts durchgeht. Es mag vielleicht leicht klingen, aber es gibt etwas zu beachten. Da "end()" hinter das letzte Element zeigt können wir es nicht als Startwert für unseren Iterator nehmen. Des Rätsels Lösung sind die Methoden "rbegin()" und "rend()". Wir müssen aber einen neuen Iterator erzeugen der Rückwert unterstützt.
CPP:
//   einen Rückwärtsiterator
std::list<int>::reverse_iterator it;

//   Wichtig! rbegin() und rend()
for(it = container.rbegin(); it != container.rend(); it++)
{
   std::cout << *it << std::endl;
}


Für Kritik und Verbesserungsvorschlägen bin ich sehr dankbar
svenburow(at)web.de

[Edit]
Rechtschtschreibfehler ausgebessert
AFE-GmdG
_________________
Nur wenn man ein Ziel sieht, kann man es auch treffen.
___________
Mein Leben, Freunde und die Spieleentwicklung


Zuletzt bearbeitet von Dragon am 27.08.2005, 19:19, insgesamt 2-mal bearbeitet
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden Website dieses Benutzers besuchen
Jonathan_Klein
Living Legend


Alter: 36
Anmeldedatum: 17.02.2003
Beiträge: 3431
Wohnort: Siegerland
Medaillen: Keine

BeitragVerfasst am: 20.08.2005, 22:01    Titel: Antworten mit Zitat

es ist nicht sehr elegant, Iteratoren die ganze zeit zu benutzen aber erst am Ende zu beschreiben...
_________________
https://jonathank.de/games/
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden E-Mail senden Website dieses Benutzers besuchen
GreveN
JLI Master


Alter: 37
Anmeldedatum: 08.01.2004
Beiträge: 901
Wohnort: Sachsen - Dresden
Medaillen: Keine

BeitragVerfasst am: 21.08.2005, 06:45    Titel: Antworten mit Zitat

Ich finde insgesamt schon sehr gut gelungen. Kommen die Assoziativen Container und Adapter noch? Wäre wirklich schade wenn nicht... Wink

Potentielle Kandidaten für einen möglichen 2. oder 3. Teil wären auch noch die fstream-Klassen, auto_ptr, strings, usw...

Gefällt mir bis jetzt wirklich gut, auch wenn ich Jonathan recht geben muss, es wäre vlt etwas eleganter, die Iteratoren nach dem ersten Container zu beschreiben. Smile
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden Yahoo Messenger MSN Messenger
AFE-GmdG
JLI MVP
JLI MVP


Alter: 44
Anmeldedatum: 19.07.2002
Beiträge: 1374
Wohnort: Irgendwo im Universum...
Medaillen: Keine

BeitragVerfasst am: 21.08.2005, 09:21    Titel: Antworten mit Zitat

Sind ein paar Schreibfehler drin (Im Code kommt öfters dieser Block vor - Im Text ist an zwei oder drei Stellen ein echter Schreibfehler enthalten), ansonsten OK.
Schick wäre es noch zu Zeigen, wie man das ganze anstellt, wenn die Vectoren, Listen usw. selbst Pointer sind, also mit New angelegt wurden, dabei gibt es nämlich noch einiges zu Beachten, vorallem beim Durchlaufen und Löschen...
_________________
CPP:
float o=0.075,h=1.5,T,r,O,l,I;int _,L=80,s=3200;main(){for(;s%L||
(h-=o,T= -2),s;4 -(r=O*O)<(l=I*I)|++ _==L&&write(1,(--s%L?_<(L)?--_
%6:6:7)+\"World! \\n\",1)&&(O=I=l=_=r=0,T+=o /2))O=I*2*O+h,I=l+T-r;}
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden E-Mail senden Website dieses Benutzers besuchen
Dragon
Super JLI'ler


Alter: 37
Anmeldedatum: 24.05.2004
Beiträge: 340
Wohnort: Sachsen
Medaillen: Keine

BeitragVerfasst am: 21.08.2005, 13:22    Titel: Antworten mit Zitat

GreveN hat Folgendes geschrieben:
Ich finde insgesamt schon sehr gut gelungen. Kommen die Assoziativen Container und Adapter noch? Wäre wirklich schade wenn nicht... Wink

Potentielle Kandidaten für einen möglichen 2. oder 3. Teil wären auch noch die fstream-Klassen, auto_ptr, strings, usw...

Gefällt mir bis jetzt wirklich gut, auch wenn ich Jonathan recht geben muss, es wäre vlt etwas eleganter, die Iteratoren nach dem ersten Container zu beschreiben. Smile


die Assoziativen Container und Adapter kommen auch noch, vieleicht mach ich noch einen 3. Teil für die <algorithm>
_________________
Nur wenn man ein Ziel sieht, kann man es auch treffen.
___________
Mein Leben, Freunde und die Spieleentwicklung
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden Website dieses Benutzers besuchen
LeeDiGer
Super JLI'ler



Anmeldedatum: 31.08.2003
Beiträge: 366
Wohnort: Duisburg
Medaillen: Keine

BeitragVerfasst am: 30.01.2008, 09:41    Titel: Antworten mit Zitat

Ist es möglich einen normalen Iterator in einen Reverse-Iterator bzw. umgekehrt zu casten?
_________________
Kein Rückzug! Kein Aufgeben!
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
GreveN
JLI Master


Alter: 37
Anmeldedatum: 08.01.2004
Beiträge: 901
Wohnort: Sachsen - Dresden
Medaillen: Keine

BeitragVerfasst am: 30.01.2008, 11:08    Titel: Antworten mit Zitat

Mit einem C++-Standard-Cast? Nein, wie auch. Du kannst aber natürlich selber einen schreiben, bzw. z.B. den =-Operator überladen (eventuell bietet die STL sogar von Haus aus diese Funktionalität, hab ich gerade nicht im Kopf und keine Lust eine Referenz zu bemühen).
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden Yahoo Messenger MSN Messenger
Deviloper
Junior JLI'ler



Anmeldedatum: 31.05.2006
Beiträge: 77

Medaillen: Keine

BeitragVerfasst am: 31.01.2008, 13:27    Titel: Antworten mit Zitat

Kommt darauf an ... bei manchen Typen sollte es gehen:
CPP:
// dein Iterator ... sagen wir mal das 10. Element
std::vector<T>::iterator it(data.begin() + 10);
std::vector<T>::reverse_iterator rit(data.rbegin() + (data.size() - (it - data.begin())));
Wink Aber naja ....
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden
DirectXer
Dark JLI'ler



Anmeldedatum: 05.02.2005
Beiträge: 1201
Wohnort: Köln
Medaillen: Keine

BeitragVerfasst am: 31.01.2008, 21:54    Titel: Antworten mit Zitat

natürlich geht das, sogar ganz einfach. baut auf dem Grundkonzept der STL auf:
CPP:
// geht mit allen containern die mindst. bidirectional_iterators spezifizieren
std::list<int>::iterator it = ....;

// it -> rit, wird automatisch convertiert
std::list<int>::reverse_iterator rit( it );

// rit -> it, ebenfalls automatisch
std::list<int>::iterator it2 = rit.base();

Falls du das selber implementieren wills, musst du darauf achten, dass dabei immer folgendes gilt:
CPP:
&*(reverse_iterator(i)) == &*(i - 1)


Gruß DXer
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden E-Mail senden
GreveN
JLI Master


Alter: 37
Anmeldedatum: 08.01.2004
Beiträge: 901
Wohnort: Sachsen - Dresden
Medaillen: Keine

BeitragVerfasst am: 01.02.2008, 10:14    Titel: Antworten mit Zitat

Ja natürlich klappt das, aber das sind eben keine expliziten Casts im eigentlichen Sinne, á la "static_cast<reverse_iterator>(it)".
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden Yahoo Messenger MSN Messenger
DirectXer
Dark JLI'ler



Anmeldedatum: 05.02.2005
Beiträge: 1201
Wohnort: Köln
Medaillen: Keine

BeitragVerfasst am: 01.02.2008, 11:57    Titel: Antworten mit Zitat

GreveN hat Folgendes geschrieben:
Ja natürlich klappt das, aber das sind eben keine expliziten Casts im eigentlichen Sinne, á la "static_cast<reverse_iterator>(it)".

Das hab ich auch nicht gesagt, aber es sollte auf jeden Fall das machen was LeeDiGer meinte. Ich hab mich aber auch eher auf Devilopers Post bezogen.
Allerdings müsste "static_cast<reverse_iterator>(it)" sogar funktionieren, da reverse_iterator einen Konstruktor mit iterator implementiert.

Gruß DXer
Nach oben
Benutzer-Profile anzeigen Private Nachricht senden E-Mail senden
Beiträge der letzten Zeit anzeigen:   
Neues Thema eröffnen   Neue Antwort erstellen    JLI Spieleprogrammierung Foren-Übersicht -> Tutorials Alle Zeiten sind GMT
Seite 1 von 1

 
Gehe zu:  
Du kannst keine Beiträge in dieses Forum schreiben.
Du kannst auf Beiträge in diesem Forum nicht antworten.
Du kannst deine Beiträge in diesem Forum nicht bearbeiten.
Du kannst deine Beiträge in diesem Forum nicht löschen.
Du kannst an Umfragen in diesem Forum nicht mitmachen.


Powered by phpBB © 2001, 2005 phpBB Group
Deutsche Übersetzung von phpBB.de

Impressum