Container-Klassen
Einführung
Die Qt-Bibliothek stellt eine Reihe von universell einsetzbaren, vorlagengestützten Containerklassen bereit. Diese Klassen können zum Speichern von Elementen eines bestimmten Typs verwendet werden. Wenn Sie beispielsweise ein in der Größe anpassbares Array von „ QString“-Objekten benötigen, verwenden Sie „ QList “ <QString>.
Diese Containerklassen sind so konzipiert, dass sie schlanker, sicherer und benutzerfreundlicher sind als die STL-Container. Wenn Sie mit der STL nicht vertraut sind oder es vorziehen, Dinge auf die „Qt-Art“ zu handhaben, können Sie diese Klassen anstelle der STL-Klassen verwenden.
Die Containerklassen werden implizit gemeinsam genutzt, sind reentrant und auf Geschwindigkeit, geringen Speicherverbrauch sowie minimale Inline-Code-Erweiterung optimiert, was zu kleineren ausführbaren Dateien führt. Darüber hinaus sind sie threadsicher, sofern sie von allen Threads, die darauf zugreifen, als schreibgeschützte Container verwendet werden.
Die Container stellen Iteratoren für die Durchquerung bereit. Iteratoren im STL-Stil sind am effizientesten und können zusammen mit den „ generic algorithms “ von Qt und der STL verwendet werden. Iteratoren im Java-Stil werden aus Gründen der Abwärtskompatibilität bereitgestellt.
Hinweis: Seit Qt 5.14 stehen für die meisten Containerklassen Bereichskonstruktoren zur Verfügung. „ QMultiMap “ bildet hier eine bemerkenswerte Ausnahme. Ihre Verwendung wird empfohlen, um die verschiedenen veralteten „from/to“-Methoden von Qt 5 zu ersetzen. Zum Beispiel:
QList<int> list = {1, 2, 3, 4, 4, 5};
QSet<int> set(list.cbegin(), list.cend());
/*
Will generate a QSet containing 1, 2, 3, 4, 5.
*/Die Containerklassen
Qt stellt die folgenden sequenziellen Container bereit: ` QList`, ` QStack` und ` QQueue`. Für die meisten Anwendungen ist ` QList ` der am besten geeignete Typ. Er ermöglicht sehr schnelles Anfügen. Wenn Sie wirklich eine verkettete Liste benötigen, verwenden Sie `std::list`. ` QStack ` und ` QQueue ` sind Komfortklassen, die LIFO- und FIFO-Semantik bereitstellen.
Qt bietet außerdem folgende assoziative Container: QMap, QMultiMap, QHash, QMultiHash und QSet. Die „Multi“-Container unterstützen auf praktische Weise mehrere Werte, die einem einzigen Schlüssel zugeordnet sind. Die „Hash“-Container ermöglichen eine schnellere Suche, indem sie eine Hash-Funktion anstelle einer binären Suche in einer sortierten Menge verwenden.
Als Sonderfälle bieten die Klassen „ QCache “ und „ QContiguousCache “ eine effiziente Hash-Suche nach Objekten in einem begrenzten Cache-Speicher.
| Klasse | Zusammenfassung |
|---|---|
| QList<T> | Dies ist bei weitem die am häufigsten verwendete Containerklasse. Sie speichert eine Liste von Werten eines bestimmten Typs (T), auf die über einen Index zugegriffen werden kann. Intern speichert sie ein Array von Werten eines bestimmten Typs an benachbarten Speicherpositionen. Das Einfügen am Anfang oder in der Mitte einer Liste kann recht langsam sein, da dies dazu führen kann, dass eine große Anzahl von Elementen im Speicher um eine Position verschoben werden muss. |
| QVarLengthArray<T, Prealloc> | Dies stellt ein Low-Level-Array mit variabler Länge bereit. Es kann an Stellen, an denen Geschwindigkeit besonders wichtig ist, anstelle von ` QList ` verwendet werden. |
| QStack<T> | Dies ist eine Komfort-Unterklasse von ` QList `, die eine „Last-in-First-out“-Semantik (LIFO) bereitstellt. Sie ergänzt die bereits in ` QList` vorhandenen Funktionen um folgende: ` push()`, ` pop()` und ` top()`. |
| QQueue<T> | Dies ist eine Komfort-Unterklasse von QList, die die „First-in-First-out“-Semantik (FIFO) bereitstellt. Sie ergänzt die bereits in QList vorhandenen Funktionen um folgende: enqueue(), dequeue() und head(). |
| QSet<T> | Dies stellt eine einwertige mathematische Menge mit schnellen Suchvorgängen bereit. |
| QMap<Key, T> | Dies stellt ein Wörterbuch (assoziatives Array) bereit, das Schlüssel vom Typ `Key` auf Werte vom Typ `T` abbildet. Normalerweise ist jedem Schlüssel ein einzelner Wert zugeordnet. ` QMap ` speichert seine Daten in der Reihenfolge der Schlüssel; wenn die Reihenfolge keine Rolle spielt, ist ` QHash ` eine schnellere Alternative. |
| QMultiMap<Key, T> | Dies stellt ein Wörterbuch bereit, ähnlich wie „ QMap “, mit dem Unterschied, dass mehrere gleichwertige Schlüssel eingefügt werden können. |
| QHash<Key, T> | Dies verfügt über fast dieselbe API wie ` QMap`, bietet jedoch deutlich schnellere Suchvorgänge. ` QHash ` speichert seine Daten in beliebiger Reihenfolge. |
| QMultiHash<Key, T> | Dies stellt ein auf einer Hash-Tabelle basierendes Wörterbuch bereit, ähnlich wie ` QHash`, mit dem Unterschied, dass mehrere gleichwertige Schlüssel eingefügt werden können. |
Container können verschachtelt werden. So ist es beispielsweise ohne Weiteres möglich, ein ` QMap<QString` sowie ein ` QList<int>>` zu verwenden, wobei der Schlüsseltyp ` QString ` und der Wertetyp ` QList<int>` ist.
Die Container sind in einzelnen Header-Dateien definiert, die denselben Namen wie der Container tragen (z. B. <QList>). Der Einfachheit halber werden die Container in <QtContainerFwd> vorwärtsdeklariert.
Die in den verschiedenen Containern gespeicherten Werte können einen beliebigen zuweisbaren Datentyp haben. Um geeignet zu sein, muss ein Typ einen Kopierkonstruktor und einen Zuweisungsoperator bereitstellen. Für einige Operationen ist zudem ein Standardkonstruktor erforderlich. Dies deckt die meisten Datentypen ab, die Sie wahrscheinlich in einem Container speichern möchten, einschließlich Grundtypen wie int und double, Zeigertypen sowie Qt-Datentypen wie QString, QDate und QTime; nicht abgedeckt sind jedoch QObject oder Unterklassen von QObject (QWidget, QDialog, QTimer usw.). Wenn Sie versuchen, ein ` QList` <QWidget> zu instanziieren, meldet der Compiler einen Fehler, da der Kopierkonstruktor und die Zuweisungsoperatoren von ` QWidget` deaktiviert sind. Wenn Sie Objekte dieser Art in einem Container speichern möchten, speichern Sie sie als Zeiger, zum Beispiel als ` QList` <QWidget *>.
Hier ist ein Beispiel für einen benutzerdefinierten Datentyp, der die Anforderungen an einen zuweisbaren Datentyp erfüllt:
class Employee
{
public:
Employee() {}
Employee(const Employee &other);
Employee &operator=(const Employee &other);
private:
QString myName;
QDate myDateOfBirth;
};Wenn wir keinen Kopierkonstruktor oder Zuweisungsoperator bereitstellen, stellt C++ eine Standardimplementierung bereit, die eine Kopie Mitglied für Mitglied durchführt. Im obigen Beispiel wäre das ausreichend gewesen. Wenn Sie zudem keine Konstruktoren bereitstellen, stellt C++ einen Standardkonstruktor bereit, der seine Elemente mithilfe von Standardkonstruktoren initialisiert. Obwohl er keine expliziten Konstruktoren oder einen Zuweisungsoperator enthält, kann der folgende Datentyp in einem Container gespeichert werden:
Einige Container stellen zusätzliche Anforderungen an die Datentypen, die sie speichern können. Beispielsweise muss der Typ „Key“ eines „ QMap “ <Key, T> die Eigenschaft „ operator<() “ erfüllen. Solche besonderen Anforderungen sind in der detaillierten Beschreibung einer Klasse dokumentiert. In einigen Fällen gelten für bestimmte Funktionen spezielle Anforderungen; diese werden für jede Funktion einzeln beschrieben. Der Compiler gibt immer eine Fehlermeldung aus, wenn eine Anforderung nicht erfüllt ist.
Die Container von Qt stellen die Operatoren `operator<<()` und `operator>>()` bereit, sodass sie mithilfe von ` QDataStream` einfach gelesen und geschrieben werden können. Das bedeutet, dass die im Container gespeicherten Datentypen ebenfalls `operator<<()` und `operator>>()` unterstützen müssen. Die Bereitstellung dieser Unterstützung ist unkompliziert; so könnten wir dies für die oben genannte Struktur `Movie` umsetzen:
QDataStream &operator<<(QDataStream &out, const Movie &movie)
{
out << (quint32)movie.id << movie.title
<< movie.releaseDate;
return out;
}
QDataStream &operator>>(QDataStream &in, Movie &movie)
{
quint32 id;
QDate date;
in >> id >> movie.title >> date;
movie.id = (int)id;
movie.releaseDate = date;
return in;
}Die Dokumentation bestimmter Funktionen von Containerklassen bezieht sich auf standardkonstruierte Werte; beispielsweise initialisiert ` QList ` seine Elemente automatisch mit standardkonstruierten Werten, und ` QMap::value()` gibt einen standardkonstruierten Wert zurück, wenn der angegebene Schlüssel nicht in der Map vorhanden ist. Für die meisten Werttypen bedeutet dies einfach, dass ein Wert mithilfe des Standardkonstruktors erstellt wird (z. B. eine leere Zeichenkette für ` QString`). Für primitive Typen wie ` int ` und ` double` sowie für Zeigertypen legt die Sprache C++ jedoch keine Initialisierung fest; in diesen Fällen initialisieren die Container von Qt den Wert automatisch auf 0.
Iteration über Container
Bereichsbasiertes „for“-Zeichen
Für Container sollte vorzugsweise die bereichsbasierte „ for “-Schleife verwendet werden:
Beachten Sie, dass bei der Verwendung eines Qt-Containers in einem Nicht-const-Kontext durch implizites Sharing eine unerwünschte Trennung des Containers erfolgen kann. Um dies zu verhindern, verwenden Sie „ std::as_const() “:
Bei assoziativen Containern durchläuft dies die Werte in einer Schleife.
Indexbasiert
Bei sequenziellen Containern, die ihre Elemente zusammenhängend im Speicher ablegen (z. B. QList), kann eine indexbasierte Iteration verwendet werden:
QList<QString> list = {"A", "B", "C", "D"};
for (qsizetype i = 0; i < list.size(); ++i) {
const auto &item = list.at(i);
//...
}Die Iterator-Klassen
Iteratoren bieten eine einheitliche Möglichkeit, auf Elemente in einem Container zuzugreifen. Die Containerklassen von Qt bieten zwei Arten von Iteratoren: Iteratoren im STL-Stil und Iteratoren im Java-Stil. Iteratoren beider Arten werden ungültig, wenn die Daten im Container geändert oder durch den Aufruf einer nicht-konstanten Memberfunktion von implizit gemeinsam genutzten Kopien getrennt werden.
Iteratoren im STL-Stil
Iteratoren im STL-Stil sind seit der Veröffentlichung von Qt 2.0 verfügbar. Sie sind mit den „ generic algorithms “ von Qt und der STL kompatibel und auf Geschwindigkeit optimiert.
Für jede Containerklasse gibt es zwei Iteratortypen im STL-Stil: einen, der Lesezugriff bietet, und einen, der Lese- und Schreibzugriff bietet. Lese-Iteratoren sollten nach Möglichkeit verwendet werden, da sie schneller sind als Lese- und Schreib-Iteratoren.
| Container | Schreibgeschützter Iterator | Lese-Schreib-Iterator |
|---|---|---|
| QList<T>, QStack<T>, QQueue<T> | QList<T>::const_iterator | QList<T>::iterator |
| QSet<T> | QSet<T>::const_iterator | QSet<T>::iterator |
| QMap<Key, T>, QMultiMap<Key, T> | QMap<Key, T>::const_iterator | QMap<Key, T>::iterator |
| QHash<Key, T>, QMultiHash<Key, T> | QHash<Key, T>::const_iterator | QHash<Key, T>::iterator |
Die API der STL-Iteratoren orientiert sich an Zeigern in einem Array. So springt der Operator „ ++ “ beispielsweise mit dem Iterator zum nächsten Element, und der Operator „ * “ gibt das Element zurück, auf das der Iterator zeigt. Tatsächlich ist bei „ QList “ und „ QStack “, die ihre Elemente an benachbarten Speicherpositionen speichern, der Typ „ iterator “ lediglich ein „typedef“ für „ T * “, und der Typ „ const_iterator “ ist lediglich ein „typedef“ für „ const T * “.
In dieser Erörterung konzentrieren wir uns auf QList und QMap. Die Iteratortypen für QSet haben genau dieselbe Schnittstelle wie die Iteratoren von QList; ebenso haben die Iteratortypen für QHash dieselbe Schnittstelle wie die Iteratoren von QMap.
Hier ist eine typische Schleife, um alle Elemente eines QList<QString> der Reihe nach zu durchlaufen und sie in Kleinbuchstaben umzuwandeln:
QList<QString> list = {"A", "B", "C", "D"};
for (auto i = list.begin(), end = list.end(); i != end; ++i)
*i = (*i).toLower();Iteratoren im STL-Stil verweisen direkt auf Elemente. Die Funktion ` begin()` eines Containers gibt einen Iterator zurück, der auf das erste Element im Container verweist. Die Funktion ` end()` eines Containers gibt einen Iterator zurück, der auf das imaginäre Element zeigt, das eine Position hinter dem letzten Element im Container liegt. ` end()` markiert eine ungültige Position; sie darf niemals dereferenziert werden. Sie wird typischerweise in der Abbruchbedingung einer Schleife verwendet. Ist die Liste leer, entspricht ` begin()` der Funktion ` end()`, sodass die Schleife niemals ausgeführt wird.
Das folgende Diagramm zeigt die gültigen Iteratorpositionen als rote Pfeile für eine Liste mit vier Elementen:
Das Rückwärtsdurchlaufen mit einem Iterator im STL-Stil erfolgt mithilfe von Reverse-Iteratoren:
QList<QString> list = {"A", "B", "C", "D"};
for (auto i = list.rbegin(), rend = list.rend(); i != rend; ++i)
*i = i->toLower();In den bisherigen Codeausschnitten haben wir den unären Operator „ * “ verwendet, um das an einer bestimmten Iteratorposition gespeicherte Element (vom Typ „ QString “) abzurufen, und anschließend die Methode „ QString::toLower()“ darauf aufgerufen.
Für den schreibgeschützten Zugriff können Sie `const_iterator`, ` cbegin()` und ` cend()` verwenden. Zum Beispiel:
for (auto i = list.cbegin(), end = list.cend(); i != end;++i)
qDebug() << *i;Die folgende Tabelle fasst die API der Iteratoren im STL-Stil zusammen:
| Ausdruck | Verhalten |
|---|---|
*i | Gibt das aktuelle Element zurück |
++i | Bewegt den Iterator zum nächsten Element |
i += n | Verschiebt den Iterator um n -Elemente vorwärts |
--i | Bewegt den Iterator um ein Element zurück |
i -= n | Bewegt den Iterator um n Elemente zurück |
i - j | Gibt die Anzahl der Elemente zwischen den Iteratoren ` i ` und j |
Die Operatoren ` ++ ` und ` -- ` sind sowohl als Präfixoperatoren (++i, --i) als auch als Postfixoperatoren (i++, i--) verfügbar. Die Präfixversionen ändern die Iteratoren und geben eine Referenz auf den geänderten Iterator zurück; die Postfixversionen erstellen vor der Änderung eine Kopie des Iterators und geben diese Kopie zurück. In Ausdrücken, in denen der Rückgabewert ignoriert wird, empfehlen wir die Verwendung der Präfixoperatoren (++i, --i), da diese etwas schneller sind.
Bei nicht-konstanten Iterator-Typen kann der Rückgabewert des unären Operators ` * ` auf der linken Seite des Zuweisungsoperators verwendet werden.
Bei ` QMap ` und ` QHash` gibt der Operator ` * ` die Wertkomponente eines Elements zurück. Wenn Sie den Schlüssel abrufen möchten, rufen Sie `key()` auf dem Iterator auf. Der Symmetrie halber stellen die Iteratortypen auch eine Funktion `value()` zum Abrufen des Werts bereit. So würden wir beispielsweise alle Elemente in einem ` QMap ` auf der Konsole ausgeben:
QMap<int, int> map;
//...
for (auto i = map.cbegin(), end = map.cend(); i != end;++i)
qDebug() << i.key() << ':' << i.value();Dank der impliziten Freigabe ist es für eine Funktion sehr ressourcenschonend, einen Container als Wert zurückzugeben. Die Qt-API enthält Dutzende von Funktionen, die einen „ QList “ oder „ QStringList “ als Wert zurückgeben (z. B. ` QSplitter::sizes()`). Wenn Sie diese mit einem STL-Iterator durchlaufen möchten, sollten Sie immer eine Kopie des Containers erstellen und die Kopie durchlaufen. Zum Beispiel:
// RIGHT
const QList<int> sizes = splitter->sizes();
for (auto i = sizes.begin(), end = sizes.end(); i != end; ++i)
{/*...*/}
// WRONG
for (auto i = splitter->sizes().begin();
i != splitter->sizes().end(); ++i)
{/*...*/}Dieses Problem tritt nicht bei Funktionen auf, die eine const- oder nicht-const-Referenz auf einen Container zurückgeben.
Problem mit implizitem Sharing von Iteratoren
Das implizite Teilen hat eine weitere Auswirkung auf Iteratoren im STL-Stil: Sie sollten es vermeiden, einen Container zu kopieren, während Iteratoren auf diesem Container aktiv sind. Die Iteratoren verweisen auf eine interne Struktur, und wenn Sie einen Container kopieren, sollten Sie mit Ihren Iteratoren sehr vorsichtig sein. Beispiel:
QList<int> a, b;
a.resize(100000); // make a big list filled with 0.
QList<int>::iterator i = a.begin();
// WRONG way of using the iterator i:
b = a;
/*
Now we should be careful with iterator i since it will point to shared data
If we do *i = 4 then we would change the shared instance (both vectors)
The behavior differs from STL containers. Avoid doing such things in Qt.
*/
a[0] = 5;
/*
Container a is now detached from the shared data,
and even though i was an iterator from the container a, it now works as an iterator in b.
Here the situation is that (*i) == 0.
*/
b.clear(); // Now the iterator i is completely invalid.
int j = *i; // Undefined behavior!
/*
The data from b (which i pointed to) is gone.
This would be well-defined with STL containers (and (*i) == 5),
but with QList this is likely to crash.
*/Das obige Beispiel zeigt zwar nur ein Problem mit ` QList`, doch das Problem besteht bei allen implizit gemeinsam genutzten Qt-Containern.
Iteratoren im Java-Stil
Iteratoren im Java-Stil orientieren sich an den Iterator-Klassen von Java. Bei neuem Code sollten Iteratoren im STL-Stil bevorzugt werden.
Qt-Container im Vergleich zu std-Containern
| Qt-Container | Am ehesten vergleichbarer std-Container |
|---|---|
| QList<T> | Ähnlich wie std::vector<T> QList und QVector wurden in Qt 6 vereinheitlicht. Beide verwenden das Datenmodell aus QVector. QVector ist nun ein Alias für QList. Das bedeutet, dass QList nicht als verkettete Liste implementiert ist. Wenn Sie also Einfügen, Löschen, Anfügen oder Voranstellen in konstanter Zeit benötigen, sollten Sie |
| QVarLengthArray<T, Prealloc> | Ähnelt einer Mischung aus std::array<T> und std::vector<T>. Aus Leistungsgründen befindet sich ` QVarLengthArray ` auf dem Stack, sofern seine Größe nicht geändert wird. Eine Größenänderung führt automatisch dazu, dass stattdessen der Heap verwendet wird. |
| QStack<T> | Ähnlich wie std::stack<T>, erbt von QList. |
| QQueue<T> | Ähnlich wie std::queue<T>, erbt von QList. |
| QSet<T> | Ähnlich wie std::unordered_set<T>. Intern wird QSet mit einem QHash implementiert. |
| QMap<Key, T> | Ähnlich wie std::map<Key, T>. |
| QMultiMap<Key, T> | Ähnlich wie std::multimap<Key, T>. |
| QHash<Key, T> | Am ehesten vergleichbar mit std::unordered_map<Key, T>. |
| QMultiHash<Key, T> | Am ehesten vergleichbar mit std::unordered_multimap<Key, T>. |
Qt-Container und std-Algorithmen
Sie können Qt-Container mit Funktionen aus #include <algorithm> verwenden.
QList<int> list = {2, 3, 1};
std::sort(list.begin(), list.end());
/*
Sort the list, now contains { 1, 2, 3 }
*/
std::reverse(list.begin(), list.end());
/*
Reverse the list, now contains { 3, 2, 1 }
*/
int even_elements =
std::count_if(list.begin(), list.end(), [](int element) { return (element % 2 == 0); });
/*
Count how many elements that are even numbers, 1
*/Qt-Container-Algorithmen
Qt stellt außerdem zusätzliche generische Algorithmen unter <QtAlgorithms> bereit, die mit jedem Container funktionieren, der Iteratoren im STL-Stil unterstützt, wie beispielsweise qJoin() zum Zusammenfügen von Containerelementen zu einem einzigen Wert und qDeleteAll() zum Aufrufen von „ operator delete “ für alle Elemente in einem Container oder in einem bestimmten Bereich.
Andere containerähnliche Klassen
Qt enthält weitere Template-Klassen, die in gewisser Hinsicht Containern ähneln. Diese Klassen stellen keine Iteratoren bereit und können nicht mit dem Schlüsselwort „ foreach “ verwendet werden.
- QCache<Key, T> stellt einen Cache zur Verfügung, in dem Objekte eines bestimmten Typs T gespeichert werden, die Schlüsseln vom Typ Key zugeordnet sind.
- QContiguousCache<T> bietet eine effiziente Möglichkeit, Daten zwischenzuspeichern, auf die typischerweise in zusammenhängender Reihenfolge zugegriffen wird.
Weitere Nicht-Template-Typen, die mit den Template-Containern von Qt konkurrieren, sind „ QBitArray “, „ QByteArray “, „ QString “ und „ QStringList “.
Algorithmische Komplexität
Bei der algorithmischen Komplexität geht es darum, wie schnell (oder langsam) eine Funktion arbeitet, wenn die Anzahl der Elemente im Container zunimmt. Beispielsweise ist das Einfügen eines Elements in die Mitte einer `std::list` eine extrem schnelle Operation, unabhängig von der Anzahl der in der Liste gespeicherten Elemente. Demgegenüber ist das Einfügen eines Elements in die Mitte einer ` QList ` potenziell sehr aufwendig, wenn die ` QList ` viele Elemente enthält, da die Hälfte der Elemente im Speicher um eine Position verschoben werden muss.
Zur Beschreibung der algorithmischen Komplexität verwenden wir die folgende Terminologie, die auf der „Big-O“-Notation basiert:
- Konstante Zeit: O(1). Eine Funktion läuft in konstanter Zeit, wenn sie unabhängig von der Anzahl der Elemente im Container immer dieselbe Zeit benötigt. Ein Beispiel hierfür ist ` QList::push_back()`.
- Logarithmische Zeit: O(log n). Eine Funktion, die in logarithmischer Zeit läuft, ist eine Funktion, deren Laufzeit proportional zum Logarithmus der Anzahl der Elemente im Container ist. Ein Beispiel ist der binäre Suchalgorithmus.
- Lineare Laufzeit: O(n). Eine Funktion, die in linearer Laufzeit läuft, wird in einer Zeit ausgeführt, die direkt proportional zur Anzahl der im Container gespeicherten Elemente ist. Ein Beispiel hierfür ist QList::insert().
- Linear-logarithmische Zeit: O(n log n). Eine Funktion, die in linear-logarithmischer Zeit läuft, ist asymptotisch langsamer als eine Funktion mit linearer Laufzeit, aber schneller als eine Funktion mit quadratischer Laufzeit.
- Quadratische Laufzeit: O(n²). Eine Funktion mit quadratischer Laufzeit wird in einer Zeit ausgeführt, die proportional zum Quadrat der Anzahl der im Container gespeicherten Elemente ist.
Die folgende Tabelle fasst die algorithmische Komplexität des sequenziellen Container- QList s <T> zusammen:
| Indexsuche | Einfügen | Anfügen | Anhängen | |
|---|---|---|---|---|
| QList<T> | O(1) | O(n) | O(n) | Amort. O(1) |
In der Tabelle steht „Amort.“ für „amortisiertes Verhalten“. Beispielsweise bedeutet „Amort. O(1)“, dass bei einem einmaligen Aufruf der Funktion zwar ein O(n)-Verhalten auftreten kann, bei mehrfachen Aufrufen (z. B. n-mal ) das durchschnittliche Verhalten jedoch O(1) beträgt.
Die folgende Tabelle fasst die algorithmische Komplexität der assoziativen Container und Mengen von Qt zusammen:
| Schlüsselsuche | Einfügen | |||
|---|---|---|---|---|
| Durchschnitt | Schlimmster Fall | Durchschnitt | Schlimmster Fall | |
| QMap<Schlüssel, T> | O(log n) | O(log n) | O(log n) | O(log n) |
| QMultiMap<Schlüssel, T> | O(log n) | O(log n) | O(log n) | O(log n) |
| QHash<Schlüssel, T> | Amortisiert O(1) | O(n) | Amort. O(1) | O(n) |
| QSet<Schlüssel> | Amort. O(1) | O(n) | Amort. O(1) | O(n) |
Mit ` QList`, ` QHash` und ` QSet` beträgt die amortisierte Zeitkomplexität beim Anfügen von Elementen O(log n). Sie lässt sich auf O(1) reduzieren, indem vor dem Einfügen der Elemente die Funktionen ` QList::reserve()`, ` QHash::reserve()` oder ` QSet::reserve()` mit der erwarteten Anzahl der Elemente aufgerufen werden. Im nächsten Abschnitt wird dieses Thema ausführlicher behandelt.
Optimierungen für primitive und verschiebbare Typen
Qt-Container können optimierte Codepfade nutzen, wenn die gespeicherten Elemente relokierbar oder sogar primitiv sind. Ob Typen primitiv oder relokierbar sind, lässt sich jedoch nicht in allen Fällen erkennen. Sie können Ihre Typen als primitiv oder relokierbar deklarieren, indem Sie das Makro ` Q_DECLARE_TYPEINFO ` mit dem Flag `Q_PRIMITIVE_TYPE` oder dem Flag `Q_RELOCATABLE_TYPE` verwenden. Weitere Details und Anwendungsbeispiele finden Sie in der Dokumentation zu ` Q_DECLARE_TYPEINFO `.
Wenn Sie ` Q_DECLARE_TYPEINFO` nicht verwenden, nutzt Qt `std::is_trivial_v<T> `, um primitive Typen zu identifizieren, und benötigt sowohl `std::is_trivially_copyable_v<T> ` als auch `std::is_trivially_destructible_v<T>`, um relokierbare Typen zu identifizieren. Dies ist immer eine sichere Wahl, wenn auch möglicherweise mit suboptimaler Leistung.
Wachstumsstrategien
QList<T>, ` QString` und ` QByteArray ` speichern ihre Elemente zusammenhängend im Speicher; ` QHash` `<Key, T>` unterhält eine Hash-Tabelle, deren Größe proportional zur Anzahl der Elemente im Hash ist. Um zu vermeiden, dass die Daten jedes Mal neu zugewiesen werden müssen, wenn ein Element am Ende des Containers hinzugefügt wird, reservieren diese Klassen in der Regel mehr Speicher als nötig.
Betrachten Sie den folgenden Code, der aus einem anderen ` QString` einen ` QString ` erstellt:
QString onlyLetters(const QString &in)
{
QString out;
for (qsizetype j = 0; j < in.size(); ++j) {
if (in.at(j).isLetter())
out += in.at(j);
}
return out;
}Wir erstellen die Zeichenkette „ out “ dynamisch, indem wir jeweils ein Zeichen an den Ende der Zeichenkette „ QString “ anhängen. Nehmen wir an, wir fügen der Zeichenkette „ “ 15.000 Zeichen an. Dann treten die folgenden 11 Neuzuweisungen (von insgesamt 15.000 möglichen) auf, wenn bei „ QString “ der Speicherplatz knapp wird: 8, 24, 56, 120, 248, 504, 1016, 2040, 4088, 8184, 16376. Am Ende sind für den String „ QString “ 16.376 Unicode-Zeichen zugewiesen, von denen 15.000 belegt sind.
Die oben genannten Werte mögen etwas seltsam erscheinen, aber es gibt ein Leitprinzip. Die Größe wird jedes Mal verdoppelt. Genauer gesagt wird zur nächsten Zweierpotenz, minus 16 Byte, übergegangen. 16 Byte entsprechen acht Zeichen, da „ QString “ intern UTF-16 verwendet.
QByteArray verwendet denselben Algorithmus wie QString, allerdings entsprechen 16 Byte dort 16 Zeichen.
QList<T> verwendet ebenfalls diesen Algorithmus, jedoch entsprechen 16 Byte 16/sizeof(T) Elementen.
QHash<Key, T> ist ein völlig anderer Fall. Die interne Hash-Tabelle von QHash wächst in Zweierpotenzen, und bei jeder Vergrößerung werden die Elemente in einen neuen Bucket verschoben, der wie folgt berechnet wird: qHash(key) % QHash::capacity() (die Anzahl der Buckets). Diese Anmerkung gilt auch für QSet<T> und QCache<Key, T>.
Für die meisten Anwendungen reicht der von Qt bereitgestellte Standard-Erweiterungsalgorithmus aus. Wenn Sie mehr Kontrolle benötigen, bieten QList<T>, QHash<KEY, T>, QSet<T>, QString und QByteArray drei Funktionen, mit denen Sie überprüfen und festlegen können, wie viel Speicher für die Speicherung der Elemente verwendet werden soll:
- capacity() gibt die Anzahl der Elemente zurück, für die Speicher zugewiesen wurde (bei ` QHash ` und ` QSet` ist dies die Anzahl der Buckets in der Hash-Tabelle).
- reserve(size) reserviert explizit Speicherplatz für size Elemente.
- squeeze() gibt den Speicher frei, der nicht zum Speichern der Elemente benötigt wird.
Wenn Sie ungefähr wissen, wie viele Elemente Sie in einem Container speichern werden, können Sie zunächst reserve() aufrufen; sobald Sie den Container vollständig befüllt haben, können Sie squeeze() aufrufen, um den zusätzlich vorab zugewiesenen Speicher freizugeben.
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.