Die einfachste Containerklasse in der Unreal Engine ist TArray. TArray ist verantwortlich für den Besitz und die Organisation einer Sequenz anderer Objekte („Elemente“ genannt) desselben Typs. Da ein TArray eine Sequenz ist, haben seine Elemente eine genau definierte Reihenfolge und seine Funktionen werden verwendet, um diese Objekte und ihre Reihenfolge deterministisch zu manipulieren.
TArray
TArray ist die am häufigsten verwendete Container-Klasse in der Unreal Engine. Sie ist schnell, speichereffzient und sicher. TArray-Typen werden durch zwei Eigenschaften definiert: einen Elementtyp und einen optionalen Allokator.
Der Elementtyp ist der Typ der Objekte, die im Array gespeichert werden. TArray ist ein homogener Container, was bedeutet, dass alle seine Elemente genau den gleichen Typ haben; Sie können Elemente verschiedenen Typs nicht in einem einzigen TArray speichern.
Der Allokator wird recht häufig weggelassen und verwendet standardmäßig einen Wert, der für die meisten Anwendungsfälle angemessen ist. Er definiert, wie die Objekte im Speicher angeordnet werden und wie das Array wachsen soll, um mehr Elemente unterzubringen. Es gibt eine Reihe verschiedener Allokatoren, die Sie nutzen können, sollte das Standardverhalten nicht für Sie geeignet sein; Sie können aber auch eigene schreiben. Mehr dazu später.
TArray ist ein Werttyp, was bedeutet, dass er ähnlich wie jeder andere eingebaute Typ, also int32 oder float, behandelt werden sollte. Er ist nicht dazu gedacht, erweitert zu werden, und das Erstellen oder Zerstören von TArray-Instanzen durch new und delete wird nicht empfohlen. Die Elemente sind gleichzeitig Werttypen und das Array besitzt sie. Die Zerstörung eines TArray führt zur Zerstörung aller darin enthaltenen Elemente. Wenn Sie eine TArray-Variable aus einer anderen erstellen, werden ihre Elemente in die neue Variable kopiert; es gibt keinen gemeinsamen Zustand.
Erstellen und Füllen eines Arrays
Um ein Array zu erstellen, definieren Sie es folgendermaßen:
TArray<int32> IntArray;Dadurch wird ein leeres Array erstellt, das eine Sequenz von Ganzzahlen enthält. Der Elementtyp kann jeder Werttyp sein, der entsprechend den normalen C++-Wertregeln kopierbar und zerstörbar ist, zum Beispiel int32, FString, TSharedPtr und so weiter. Es wurde kein Allokator spezifiziert, also verwendet das TArray den standardmäßigen, Heap-basierten Allokator. An diesem Punkt wurde noch kein Speicher zugewiesen.
TArray (wie viele Unreal-Engine-Container) geht davon aus, dass der Elementtyp trivial verschiebbar ist, was bedeutet, dass Elemente sicher von einem Ort im Speicher an einen anderen verschoben werden können, indem die Roh-Bytes direkt kopiert werden.
TArrays können auf verschiedene Weisen gefüllt werden. Eine Möglichkeit ist die Init-Funktion, die ein Array mit einer Reihe von Kopien eines Elements füllt:
IntArray.Init(10, 5);
// IntArray == [10,10,10,10,10]Die Funktionen Add und Emplace können neue Elemente am Ende des Arrays erstellen:
TArray<FString> StrArr;
StrArr.Add (TEXT("Hello"));
StrArr.Emplace(TEXT("World"));
// StrArr == ["Hello","World"]Der Allokator des Arrays stellt nach Bedarf Speicher zur Verfügung, wenn neue Elemente zum Array hinzugefügt werden. Der Standard-Allokator fügt genug Speicher für mehrere neue Elemente hinzu, wenn die aktuelle Array-Größe überschritten wird. Add und Emplace tun im Grunde das Gleiche, aber mit einem kleinen Unterschied:
Add(oderPush) wird eine Instanz des Elements in das Array kopieren (oder verschieben).Emplacewird die Argumente verwenden, die Sie übergeben, um eine neue Instanz des Elements zu konstruieren.
Im Fall von TArray<FString> erstellt Add einen temporären FString aus dem String-Literal und verschiebt dann die Inhalte dieses temporären FString in einen neuen FString innerhalb des Containers, während Emplace den neuen FString direkt mit dem String-Literal erstellt. Das Endergebnis ist das gleiche, aber Emplace vermeidet die Erstellung einer temporären Variable, was bei nicht-trivialen Werten wie FString oft nicht wünschenswert ist.
Im Allgemeinen ist Emplace der Funktion Add vorzuziehen, da damit unnötige temporäre Variablen an der aufrufenden Stelle vermieden werden, die dann in den Container kopiert oder verschoben werden. In der Regel verwenden Sie Add für triviale Typen und Emplace in anderen Fällen. Emplace ist nie weniger effizient als Add, aber Add kann besser lesbar sein.
Append fügt mehrere Elemente auf einmal von einem anderen TArray oder einem Pointer zu einem regulären C-Array und der Größe dieses Arrays hinzu:
FString Arr[] = { TEXT("of"), TEXT("Tomorrow") };
StrArr.Append(Arr, ARRAY_COUNT(Arr));
// StrArr == ["Hello","World","of","Tomorrow"]AddUnique fügt dem Container nur dann ein neues Element hinzu, wenn nicht bereits ein äquivalentes Element existiert. Die Gleichwertigkeit wird mit dem operator== des Elementtyps geprüft:
StrArr.AddUnique(TEXT("!"));
// StrArr == ["Hello","World","of","Tomorrow","!"]
StrArr.AddUnique(TEXT("!"));
// StrArr is unchanged as "!" is already an elementInsert fügt wie Add, Emplace und Append ein einzelnes Element oder eine Kopie eines Arrays von Elementen an einem bestimmten Index hinzu:
StrArr.Insert(TEXT("Brave"), 1);
// StrArr == ["Hello","Brave","World","of","Tomorrow","!"]Die SetNum-Funktion kann die Anzahl der Array-Elemente direkt festlegen, wobei neue Elemente unter Verwendung des Standard-Constructor des Elements erstellt werden, wenn die neue Anzahl größer als die aktuelle ist:
StrArr.SetNum(8);
// StrArr == ["Hello","Brave","World","of","Tomorrow","!","",""]SetNum entfernt auch Elemente, wenn die neue Anzahl kleiner als die aktuelle ist. Mehr Einzelheiten zum Entfernen von Elementen folgen später:
StrArr.SetNum(6);
// StrArr == ["Hello","Brave","World","of","Tomorrow","!"]Iteration
Es gibt mehrere Wege, über die Elemente Ihres Arrays zu iterieren, aber die empfohlene Methode ist die Nutzung der C++-ranged-for-Funktion:
FString JoinedStr;
for (auto& Str : StrArr)
{
JoinedStr += Str;
JoinedStr += TEXT(" ");
}
// JoinedStr == "Hello Brave World of Tomorrow ! "Normale indexbasierte Iteration ist natürlich auch möglich:
for (int32 Index = 0; Index != StrArr.Num(); ++Index)
{
JoinedStr += StrArr[Index];
JoinedStr += TEXT(" ");
}Arrays haben auch einen eigenen Iterator für mehr Kontrolle über Ihre Iteration. Es gibt zwei Funktionen namens CreateIterator und CreateConstIterator, die für Lese-Schreib- oder schreibgeschützten Zugang auf die Elemente verwendet werden können:
for (auto It = StrArr.CreateConstIterator(); It; ++It)
{
JoinedStr += *It;
JoinedStr += TEXT(" ");
}Sortierung
Arrays können einfach durch Abrufen der Sort-Funktion sortiert werden:
StrArr.Sort();
// StrArr == ["!","Brave","Hello","of","Tomorrow","World"]Hier werden die Werte nach dem operator< des Elementtyps sortiert. Im Fall von FString ist das ein lexikalischer Vergleich, bei dem die Groß-/Kleinschreibung nicht beachtet wird. Ein binäres Prädikat kann ebenfalls implementiert werden, um verschiedene Sortierungssemantiken wie folgt bereitzustellen:
StrArr.Sort([](const FString& A, const FString& B) {
return A.Len() < B.Len();
});
// StrArr == ["!","of","Hello","Brave","World","Tomorrow"]Jetzt werden die Strings nach ihrer Länge sortiert. Beachten Sie, wie die drei gleich langen Strings – "Hello", "Brave" und „World" – ihre Reihenfolge im Verhältnis zu ihren vorherigen Positionen im Array geändert haben. Das liegt daran, dass Sort instabil ist und die relative Reihenfolge äquivalenter Elemente (diese Strings sind hier äquivalent, da das Prädikat nur die Länge vergleicht) nicht garantiert wird. Sort ist als Quicksort implementiert.
Die HeapSort-Funktion kann mit oder ohne binäres Prädikat verwendet werden, um eine Heap-Sortierung durchzuführen. Ob Sie sich dazu entscheiden, sie zu verwenden, hängt von Ihren speziellen Daten ab und davon, wie effizient diese im Vergleich zur Funktion sortiert werden. Ebenso wie Sort ist HeapSort nicht stabil. Wenn wir HeapSort anstatt Sort verwendet haben, wäre das Ergebnis (dasselbe in diesem Fall):
StrArr.HeapSort([](const FString& A, const FString& B) {
return A.Len() < B.Len();
});
// StrArr == ["!","of","Hello","Brave","World","Tomorrow"]Zu guter Letzt kann StableSort benutzt werden, um die relative Reihenfolge äquivalenter Elemente nach der Sortierung zu garantieren. Wenn wir oben StableSort anstatt Sort oder HeapSort aufgerufen hätten, wäre das Ergebnis wie folgt gewesen:
StrArr.StableSort([](const FString& A, const FString& B) {
return A.Len() < B.Len();
});
// StrArr == ["!","of","Brave","Hello","World","Tomorrow"]Das heißt, "Brave", "Hello" und „World" bleiben in ihrer gleichen relativen Reihenfolge, nachdem sie lexikografisch sortiert wurden. StableSort wird als Mergesort implementiert.
Anfragen
Mit der Num-Funktion können wir das Array fragen, wie viele Elemente es enthält:
int32 Count = StrArr.Num();
// Count == 6Wenn Sie direkten Zugang auf den Array-Speicher benötigen, etwa für Interoperabilität mit einer API im C-Stil, können Sie die GetData-Funktion verwenden, um einen Zeiger zu den Elementen im Array zurückzugeben. Dieser Pointer ist nur so lange gültig, wie das Array existiert und bevor Mutationen am Array vorgenommen werden. Nur die ersten Num-Indexe aus dem StrPtr sind dereferenzierbar:
FString* StrPtr = StrArr.GetData();
// StrPtr[0] == "!"
// StrPtr[1] == "of"
// ...
// StrPtr[5] == "Tomorrow"
// StrPtr[6] - undefined behaviorIst der Container const, ist der zurückgegebene Pointer ebenfalls const.
Sie können den Container auch fragen, wie groß die Elemente sind:
uint32 ElementSize = StrArr.GetTypeSize();
// ElementSize == sizeof(FString)Um Elemente abzurufen, können Sie den Indexierungsoperator[] verwenden und ihm einen null-basierten Index auf das gewünschte Element übergeben:
FString Elem1 = StrArr[1];
// Elem1 == "of"Das Übergeben eines ungültigen Index – kleiner als 0 oder größer als oder gleich Num() – führt zu einem Laufzeitfehler. Sie können den Container abfragen, ob ein bestimmter Index gültig ist, indem Sie die IsValidIndex-Funktion verwenden:
bool bValidM1 = StrArr.IsValidIndex(-1);
bool bValid0 = StrArr.IsValidIndex(0);
bool bValid5 = StrArr.IsValidIndex(5);
bool bValid6 = StrArr.IsValidIndex(6);
// bValidM1 == false
// bValid0 == true
// bValid5 == true
// bValid6 == falseoperator[] gibt eine Referenz zurück, also können Sie dies auch nutzen, um die Elemente innerhalb des Arrays zu verändern, wenn das Array nicht konstant ist:
StrArr[3] = StrArr[3].ToUpper();
// StrArr == ["!","of","Brave","HELLO","World","Tomorrow"]Ähnlich wie die GetData-Funktion gibt operator[] eine const-Referenz zurück, wenn das Array const ist. Sie können das Array auch vom Ende des Arrays rückwärts indexieren, indem Sie die Last-Funktion verwenden. Der Index ist standardmäßig null. Die Top-Funktion ist ein Synonym für Last, nimmt aber keinen Index entgegen:
FString ElemEnd = StrArr.Last();
FString ElemEnd0 = StrArr.Last(0);
FString ElemEnd1 = StrArr.Last(1);
FString ElemTop = StrArr.Top();
// ElemEnd == "Tomorrow"
// ElemEnd0 == "Tomorrow"
// ElemEnd1 == "World"
// ElemTop == "Tomorrow"Wir können das Array fragen, ob es ein bestimmtes Element enthält:
bool bHello = StrArr.Contains(TEXT("Hello"));
bool bGoodbye = StrArr.Contains(TEXT("Goodbye"));
// bHello == true
// bGoodbye == falseOder wir können das Array fragen, ob es ein Element enthält, das zu einem bestimmten Prädikat passt:
bool bLen5 = StrArr.ContainsByPredicate([](const FString& Str){
return Str.Len() == 5;
});
bool bLen6 = StrArr.ContainsByPredicate([](const FString& Str){
return Str.Len() == 6;
});
// bLen5 == true
// bLen6 == falseElemente können wir mit der Find-Funktionsgruppe finden. Um zu prüfen, ob ein Element existiert, und seinen Index zurückzugeben, verwenden wir Find:
int32 Index;
if (StrArr.Find(TEXT("Hello"), Index))
{
// Index == 3
}Das setzt Index auf den Index des ersten gefundenen Elements. Wenn es doppelte Elemente gibt und wir stattdessen den Index des letzten Elements finden wollen, verwenden wir stattdessen die Funktion FindLast:
int32 IndexLast;
if (StrArr.FindLast(TEXT("Hello"), IndexLast))
{
// IndexLast == 3, because there aren't any duplicates
}Beide Funktionen geben einen booleschen Wert zurück, der anzeigt, ob ein Element gefunden wurde oder nicht, und schreiben den Index dieses Elements in eine Variable, wenn es gefunden wurde.
Find und FindLast können einen Elementindex auch direkt zurückgeben. Sie werden dies tun, wenn Sie den Index nicht als explizites Argument übergeben. Das kann prägnanter sein als die obige Funktion. Welche Funktion Sie verwenden, hängt von Ihren Bedürfnissen und Ihrem Stil ab.
Wenn kein Element gefunden wurde, wird der besondere Wert INDEX_NONE zurückgegeben:
int32 Index2 = StrArr.Find(TEXT("Hello"));
int32 IndexLast2 = StrArr.FindLast(TEXT("Hello"));
int32 IndexNone = StrArr.Find(TEXT("None"));
// Index2 == 3
// IndexLast2 == 3
// IndexNone == INDEX_NONEIndexOfByKey funktioniert ähnlich, aber ermöglicht den Vergleich der Elemente mit einem beliebigen Objekt. Mit den Find-Funktionen wird das Argument tatsächlich in den Elementtyp (in diesem Fall FString) konvertiert, bevor die Suche beginnt. Mit IndexOfByKey wird der Schlüssel direkt verglichen, wodurch Suchen auch dann unterstützt werden, wenn der Schlüsseltyp nicht direkt in das Element umgewandelt werden kann.
IndexOfByKey funktioniert für jeden Schlüsseltyp, für den operator==(ElementType, KeyType) existiert. IndexOfByKey gibt den Index des ersten gefundenen Elements aus, oder INDEX_NONE, wenn kein Element gefunden wurde:
int32 Index = StrArr.IndexOfByKey(TEXT("Hello"));
// Index == 3Die Funktion IndexOfByPredicate findet den Index des ersten Elements, das dem spezifizierten Prädikat entspricht, und gibt wieder den besonderen Wert INDEX_NONE zurück, wenn keines gefunden wurde:
int32 Index = StrArr.IndexOfByPredicate([](const FString& Str){
return Str.Contains(TEXT("r"));
});
// Index == 2Anstatt Indizes zurückzugeben, können wir Pointer auf die gefundenen Elemente zurückgeben. FindByKey funktioniert wie IndexOfByKey und vergleicht die Elemente mit einem beliebigen Objekt, gibt aber einen Pointer auf das gefundene Element zurück. Findet es kein Element, gibt sie nullptr zurück.
auto* OfPtr = StrArr.FindByKey(TEXT("of")));
auto* ThePtr = StrArr.FindByKey(TEXT("the")));
// OfPtr == &StrArr[1]
// ThePtr == nullptrFindByPredicate kann wie IndexOfByPredicate verwendet werden, allerdings gibt es einen Pointer statt eines Index zurück:
auto* Len5Ptr = StrArr.FindByPredicate([](const FString& Str){
return Str.Len() == 5;
});
auto* Len6Ptr = StrArr.FindByPredicate([](const FString& Str){
return Str.Len() == 6;
});
// Len5Ptr == &StrArr[2]
// Len6Ptr == nullptrZu guter Letzt können Sie mit der FilterByPredicate-Funktion ein Array von Elementen abrufen , die einem bestimmten Prädikat entsprechen:
auto Filter = StrArray.FilterByPredicate([](const FString& Str){
return !Str.IsEmpty() && Str[0] < TEXT('M');
});Entfernung
Sie können Elemente mit der Remove-Funktionsgruppe aus dem Array löschen. Die Remove-Funktion entfernt alle Elemente, die entsprechend der operator==-Funktion des Elementtyps als gleich dem von Ihnen bereitgestellten Element angesehen werden. Zum Beispiel:
TArray<int32> ValArr;
int32 Temp[] = { 10, 20, 30, 5, 10, 15, 20, 25, 30 };
ValArr.Append(Temp, ARRAY_COUNT(Temp));
// ValArr == [10,20,30,5,10,15,20,25,30]
ValArr.Remove(20);
// ValArr == [10,30,5,10,15,25,30]Sie können auch RemoveSingle verwenden, um das erste übereinstimmende Element im Array zu löschen. Das ist nützlich, wenn Sie wissen, dass Ihr Array möglicherweise Duplikate enthalten kann und Sie nur eines löschen wollen, oder als Optimierung, wenn Sie wissen, dass Ihr Array immer nur ein passendes Element enthalten kann:
ValArr.RemoveSingle(30);
// ValArr == [10,5,10,15,25,30]Wir können Elemente auch anhand ihres null-basierten Index entfernen, indem wir die Funktion RemoveAt verwenden. Möglicherweise möchten Sie IsValidIndex verwenden, um zu überprüfen, ob das Array ein Element mit dem Index hat, den Sie bereitstellen möchten, da die Übergabe eines ungültigen Index an diese Funktion einen Laufzeitfehler auslöst.
ValArr.RemoveAt(2); // Removes the element at index 2
// ValArr == [10,5,15,25,30]
ValArr.RemoveAt(99); // This will cause a runtime error as
// there is no element at index 99Mit der RemoveAll-Funktion können wir Elemente entfernen, die einem Prädikat entsprechen. Zum Beispiel alle Werte entfernen, die ein Vielfaches von 3 sind:
ValArr.RemoveAll([](int32 Val) {
return Val % 3 == 0;
});
// ValArr == [10,5,25]In all diesen Fällen wurden, wenn Elemente entfernt wurden, die folgenden Elemente in niedrigere Indizes gemischt, da im Array keine Löcher zurückbleiben können.
Der Mischprozess hat einen Overhead. Wenn es Ihnen egal ist, in welcher Reihenfolge die restlichen Elemente verbleiben, können Sie diesen Overhead mit den Funktionen RemoveSwap, RemoveAtSwap und RemoveAllSwap reduzieren. Diese funktionieren wie ihre Nicht-Swap-Varianten, garantieren jedoch nicht die Reihenfolge der verbleibenden Elemente, wodurch sie ihre Aufgaben schneller ausführen können:
TArray<int32> ValArr2;
for (int32 i = 0; i != 10; ++i)
ValArr2.Add(i % 5);
// ValArr2 == [0,1,2,3,4,0,1,2,3,4]
ValArr2.RemoveSwap(2);
// ValArr2 == [0,1,4,3,4,0,1,3]
ValArr2.RemoveAtSwap(1);
// ValArr2 == [0,3,4,3,4,0,1]
Die Empty-Funktion schließlich entfernt sämtliche Daten aus dem Array:
ValArr2.Empty();
// ValArr2 == []Operatoren
Arrays sind reguläre Werttypen und können als solche mit dem Standard-Constructor oder dem Zuweisungsoperator kopiert werden. Da Arrays ihre Elemente vollständig besitzen, erfolgt das Kopieren eines Arrays vollständig, sodass das neue Array über eine eigene Kopie der Elemente verfügt:
TArray<int32> ValArr3;
ValArr3.Add(1);
ValArr3.Add(2);
ValArr3.Add(3);
auto ValArr4 = ValArr3;
// ValArr4 == [1,2,3];
ValArr4[0] = 5;
// ValArr3 == [1,2,3];
// ValArr4 == [5,2,3];Als Alternative zur Anfügen-Funktion können Sie Arrays mit operator+= verketten:
ValArr4 += ValArr3;
// ValArr4 == [5,2,3,1,2,3]TArray unterstützt auch Bewegungsemantik, die mit der MoveTemp-Funktion aufgerufen werden kann. Nach einem Verschieben ist das Quell-Array garantiert leer:
ValArr3 = MoveTemp(ValArr4);
// ValArr3 == [5,2,3,1,2,3]
// ValArr4 == []Arrays können mit operator== und operator!= verglichen werden. Die Reihenfolge der Elemente ist wichtig – zwei Arrays sind nur dann gleich, wenn sie die gleiche Anzahl von Elementen in derselben Reihenfolge aufweisen. Elemente werden mittels ihres eigenen operator== verglichen:
TArray<FString> FlavorArr1;
FlavorArr1.Emplace(TEXT("Chocolate"));
FlavorArr1.Emplace(TEXT("Vanilla"));
// FlavorArr1 == ["Chocolate","Vanilla"]
auto FlavorArr2 = Str1Array;
// FlavorArr2 == ["Chocolate","Vanilla"]
bool bComparison1 = FlavorArr1 == FlavorArr2;
// bComparison1 == true
Heap
TArray hat Funktionen, die eine binäre Heap-Datenstruktur unterstützen. Ein Heap ist ein Typ von Binärbaum, in dem jeder Parent-Knoten allen Child-Knoten entspricht oder vor ihnen angeordnet ist. Bei der Implementierung als Array befindet sich der Stammknoten des Baums bei Element 0 und die Indizes des linken und rechten Knotens eines Knotens am Index N sind jeweils 2N+1 und 2N+2. Die Children stehen in keiner bestimmten Reihenfolge zueinander.
Jedes bestehende Array kann in einen Heap umgewandelt werden, indem Sie die Heapify-Funktion aufrufen. Dies wird überladen, um ein Prädikat zu nehmen oder nicht, wobei die Version ohne Prädikat den operator< des Elementtyps verwendet, um die Reihenfolge zu bestimmen:
TArray<int32> HeapArr;
for (int32 Val = 10; Val != 0; --Val)
{
HeapArr.Add(Val);
}
// HeapArr == [10,9,8,7,6,5,4,3,2,1]
HeapArr.Heapify();
// HeapArr == [1,2,4,3,6,5,8,10,7,9]Hier ist eine Visualisierung des Baums:
Die Knoten im Baum können von links nach rechts und von oben nach unten gelesen werden als die Reihenfolge der Elemente im heapifizierten Array. Beachte, dass das Array nicht unbedingt sortiert wird, nachdem es in einen Heap umgewandelt wurde. Ein sortiertes Array wäre zwar auch ein gültiger Heap, aber die Definition der Heap-Struktur ist lose genug, um mehrere gültige Heaps für denselben Elementsatz zu erlauben.
Neue Elemente können über die HeapPush-Funktion zum Heap hinzugefügt werden, um andere Knoten neu anzuordnen und den Heap aufrechtzuerhalten:
HeapArr.HeapPush(4);
// HeapArr == [1,2,4,3,4,5,8,10,7,9,6]Die Funktionen HeapPop und HeapPopDiscard werden verwendet, um den oberen Knoten vom Heap zu entfernen. Der Unterschied zwischen beiden ist, dass erstere eine Referenz zu einem Element nimmt, um eine Kopie des oberen Elements zurückzugeben, während letztere einfach den oberen Knoten entfernt, ohne ihn zurückzugeben. Beide Funktionen führen zur gleichen Änderung des Arrays, und der Heap wird wiederum durch die entsprechende Neuanordnung anderer Elemente erhalten:
int32 TopNode;
HeapArr.HeapPop(TopNode);
// TopNode == 1
// HeapArr == [2,3,4,6,4,5,8,10,7,9]HeapRemoveAt entfernt ein Element an einem bestimmten Index aus dem Array und ordnet die Elemente dann neu, um den Heap zu erhalten:
HeapArr.HeapRemoveAt(1);
// HeapArr == [2,4,4,6,9,5,8,10,7]HeapPush, HeapPop, HeapPopDiscard und HeapRemoveAt sollten nur abgerufen werden, wenn die Struktur bereits ein gültiger Heap ist, etwa nach einem Heapify-Aufruf, einer anderen Heap-Operation oder durch manuelle Umwandlung des Arrays in einen Heap.
Jede dieser Funktionen, einschließlich Heapify, kann ein optionales binäres Prädikat verwenden, um die Reihenfolge der Knoten im Heap zu bestimmen. Standardmäßig verwenden Heap-Operationen den operator< des Elementtyps, um die Reihenfolge zu bestimmen. Bei der Verwendung eines benutzerdefinierten Prädikats ist es wichtig, dasselbe Prädikat bei allen Heap-Operationen zu verwenden.
Schließlich kann der oberste Knoten des Heaps mit HeapTop untersucht werden, ohne das Array zu ändern:
int32 Top = HeapArr.HeapTop();
// Top == 2Slack
Da Arrays ihre Größe ändern können, verbrauchen sie variabel viel Speicher. Um eine Neuzuweisung bei jedem Hinzufügen von Elementen zu vermeiden, stellen Allokatoren in der Regel mehr Speicher bereit als angefordert wurde, damit zukünftige Add-Aufrufe keine Performance-Einbußen durch Neuzuweisung erleiden. Gleichermaßen gibt das Entfernen von Elementen in der Regel keinen Speicher frei. Dadurch enthält das Array nicht genutzte Elemente (Slack-Elemente), bei denen es sich um vorab zugewiesene Elemente handelt, die momentan nicht verwendet werden. Die Menge des Slacks in einem Array wird als die Differenz zwischen der Anzahl der im Array gespeicherten Elemente und der Anzahl der Elemente definiert, die das Array mit dem ihm zugewiesenen Speicherplatz speichern könnte.
Da ein standardmäßig aufgebautes Array keinen Speicher zuweist, ist der Slack anfänglich gleich Null. Sie können herausfinden, wie viel Slack ein Array hat, indem Sie die GetSlack-Funktion verwenden. Die maximale Anzahl von Elementen, die das Array enthalten kann, bevor der Container neu zugewiesen wird, kann mit der Max-Funktion abgerufen werden. GetSlack entspricht der Differenz zwischen Max und Num:
TArray<int32> SlackArray;
// SlackArray.GetSlack() == 0
// SlackArray.Num() == 0
// SlackArray.Max() == 0
SlackArray.Add(1);
// SlackArray.GetSlack() == 3
// SlackArray.Num() == 1
// SlackArray.Max() == 4
Die Menge des Slacks in einem Container nach einer Neuzuweisung wird durch den Allokator entschieden, Nutzer sollten sich also nicht darauf verlassen, dass der Slack konstant bleibt.
Slack-Management ist zwar nicht erforderlich, aber Sie können sie zu Ihrem Vorteil nutzen, um dem Array Optimierungshinweise zu geben. Wenn Sie zum Beispiel wissen, dass Sie 100 neue Elemente zum Array hinzufügen werden, können Sie sicherstellen, dass Sie vor dem Hinzufügen einen Slack von mindestens 100 haben, damit das Array keinen Speicher allokieren muss, während es die neuen Elemente hinzufügt. Die oben erwähnte Empty-Funktion akzeptiert ein optionales Slack-Argument:
SlackArray.Empty();
// SlackArray.GetSlack() == 0
// SlackArray.Num() == 0
// SlackArray.Max() == 0
SlackArray.Empty(3);
// SlackArray.GetSlack() == 3
// SlackArray.Num() == 0
// SlackArray.Max() == 3
SlackArray.Add(1);
SlackArray.Add(2);
Es gibt eine Reset-Funktion, die ähnlich wie Empty funktioniert, außer dass sie keinen Speicher freigibt, wenn der angeforderte Slack bereits durch die aktuelle Allokation bereitgestellt wird. Allerdings wird dies mehr Speicher allokieren, wenn der angefragte Slack größer ist:
SlackArray.Reset(0);
// SlackArray.GetSlack() == 3
// SlackArray.Num() == 0
// SlackArray.Max() == 3
SlackArray.Reset(10);
// SlackArray.GetSlack() == 10
// SlackArray.Num() == 0
// SlackArray.Max() == 10Zu guter Letzt können Sie sämtlichen Slack mit der Shrink-Funktion entfernen, die die Allokation auf die minimale Größe ändern wird, die für die aktuellen Elemente erforderlich ist. Shrink hat keinen Effekt auf die Elemente im Array:
SlackArray.Add(5);
SlackArray.Add(10);
SlackArray.Add(15);
SlackArray.Add(20);
// SlackArray.GetSlack() == 6
// SlackArray.Num() == 4
// SlackArray.Max() == 10
SlackArray.Shrink();
// SlackArray.GetSlack() == 0
// SlackArray.Num() == 4
Rohspeicher
TArray ist letztlich nur ein Wrapper, der zugewiesenen Speicher umschließt. Es kann nützlich sein, sie als solche zu behandeln, indem Sie die Bytes der Allokation direkt modifizieren und selbst Elemente erstellen. TArray wird stets versuchen, das Beste aus den verfügbaren Informationen zu machen, aber manchmal müssen Sie auf eine niedrigere Ebene zurückspringen.
Die folgenden Funktionen gewähren Ihnen einen schnellen, Low-Level-Zugang auf TArray und die darin enthaltenen Daten. Bei unsachgemäßer Verwendung können Sie den Container in ungültige Zustände versetzen und undefiniertes Verhalten auslösen. Es ist Ihnen überlassen, den Container nach Aufruf dieser Funktionen, aber bevor eine andere reguläre Funktion abgerufen wird, auf einen gültigen Zustand zurückzusetzen.
Die Funktionen AddUninitialized und InsertUninitialized fügen dem Array etwas nicht initialisierten Raum hinzu. Sie funktionieren wie die Funktionen Add und Insert, aber sie rufen nicht den Constructor des Elementtyps auf. Dies kann nützlich sein, um das Aufrufen von Constructoren zu vermeiden. Das könnten Sie in Fällen wie dem folgenden Beispiel tun, in dem Sie die gesamte Struktur mit einem Memcpy-Aufruf überschreiben wollen:
int32 SrcInts[] = { 2, 3, 5, 7 };
TArray<int32> UninitInts;
UninitInts.AddUninitialized(4);
FMemory::Memcpy(UninitInts.GetData(), SrcInts, 4*sizeof(int32));
// UninitInts == [2,3,5,7]Sie können diese Funktion auch nutzen, um Speicher für Objekte zu reservieren, die Sie selbst erstellen möchten:
TArray<FString> UninitStrs;
UninitStrs.Emplace(TEXT("A"));
UninitStrs.Emplace(TEXT("D"));
UninitStrs.InsertUninitialized(1, 2);
new ((void*)(UninitStrs.GetData() + 1)) FString(TEXT("B"));
new ((void*)(UninitStrs.GetData() + 2)) FString(TEXT("C"));
// UninitStrs == ["A","B","C","D"]AddZeroed und InputZeroed funktionieren ähnlich, aber sie setzen auch die Bytes des hinzugefügten/eingefügten Raums auf Null:
struct S
{
S(int32 InInt, void* InPtr, float InFlt)
: Int(InInt)
, Ptr(InPtr)
, Flt(InFlt)
{
}
int32 Int;
void* Ptr;
Es gibt auch die Funktionen SetNumUninitialized und SetNumZeroed, die wie SetNum funktionieren, mit der Ausnahme, dass in dem Fall, in dem die neue Zahl größer als die aktuelle ist, der Raum für die neuen Elemente nicht initialisiert bzw. bitweise auf Null gesetzt wird. Wie bei den Funktionen AddUninitialized und InputUninitialized sollten Sie bei Bedarf sicherstellen, dass neue Elemente korrekt in den neuen Raum konstruiert werden, wenn folgende Voraussetzungen erfüllt sein müssen:
SArr.SetNumUninitialized(3);
new ((void*)(SArr.GetData() + 1)) S(5, (void*)0x12345678, 3.14);
new ((void*)(SArr.GetData() + 2)) S(2, (void*)0x87654321, 2.72);
// SArr == [
// { Int: 0, Ptr: nullptr, Flt: 0.0f },
// { Int: 5, Ptr: 0x12345678, Flt: 3.14f },
// { Int: 2, Ptr: 0x87654321, Flt: 2.72f }
// ]
SArr.SetNumZeroed(5);
Verwenden Sie die “„nicht initialisieren“ und „genullten“ Funktionsfamilien mit Vorsicht. Wenn ein Elementtyp ein Mitglied umfasst, das eine Konstruktion benötigt oder keinen gültigen, bitweise auf Null gesetzten Zustand hat, kann dies zu ungültigen Array-Elementen und undefiniertem Verhalten führen. Diese Funktionen sind am nützlichsten für Arrays von Typen, die sich wahrscheinlich nie ändern, wie FMatrix oder FVector.
Verschiedenes
Die BulkSerialize-Funktion ist eine Serialisierung, die als alternativer operator<< verwendet werden kann, um das Array als Block mit Roh-Bytes zu serialisieren, anstatt die Serialisierung pro Element durchzuführen. Das kann die Performance bei trivialen Elementen verbessern, wie einem eingebauten Typ oder einer einfachen Datenstruktur.
Die Funktionen CountBytes und GetAllocatedSize werden zur Bestimmung verwendet, wie viel Speicher aktuell vom Array genutzt wird. CountBytes benötigt ein FArchive, während GetAllocatedSize direkt aufgerufen werden kann. Diese Funktionen werden in der Regel für die Berichterstattung über Statistiken verwendet.
Die Funktionen Swap und SwapMemory nehmen beide zwei Indizes entgegen und tauschen den Wert der Elemente an diesen Indizes aus. Sie sind gleichwertig, außer dass Swap eine zusätzliche Fehlerprüfung der Indizes durchführt und per assert prüft, ob die Indizes außerhalb des gültigen Bereichs liegen.