UE5 C++ TArray
概述
- TArray 是UE4中最常用的容器类。其速度快、内存消耗小、安全性高
- TArray 类型由两大属性定义:元素类型和可选分配器
- 元素类型是存储在数组中的对象类型。TArray 被称为同质容器。换言之,其所有元素均完全为相同类型。单个 TArray 中不能存储不同类型的元素。
- 分配器常被省略,默认为最常用的分配器。其定义对象在内存中的排列方式;以及数组如何进行扩展,以容纳更多的元素。
创建
TArray<int32> IntArray;
添加元素
Init
将大量元素副本填入数组
IntArray.Init(10, 5);
// IntArray == [10,10,10,10,10]
Add
用于在数组末尾新建对象 添加时创建临时变量,复制(或移动)到数组中Emplace
用于在数组末尾新建对象,不创建临时变量Append
可一次性添加其他 TArray 中的多个元素,或者指向常规C数组的指针及该数组的大小
FString Arr[] = { TEXT("of"), TEXT("Tomorrow") };
StrArr.Append(Arr, ARRAY_COUNT(Arr));
// StrArr == ["Hello","World","of","Tomorrow"]
AddUnique
仅在尚不存在等值元素时,才会向容器添加新元素Insert
在给定索引处添加单个元素或元素数组的副本
StrArr.Insert(TEXT("Brave"), 1);
SetNum
函数可直接设置数组元素的数量- 如新数量大于当前数量,则使用元素类型的默认构造函数新建元素
- 如新数量小于当前数量,SetNum 将移除元素
迭代
- C++的范围(ranged-for)功能
FString JoinedStr;
for (auto& Str :StrArr)
{
JoinedStr += Str;
JoinedStr += TEXT(" ");
}
- 使用基于索引的常规迭代
for (int32 Index = 0; Index != StrArr.Num(); ++Index)
{
JoinedStr += StrArr[Index];
JoinedStr += TEXT(" ");
}
- 数组迭代器类型控制迭代
- CreateIterator用于元素的读写
- CreateConstIterator 用于元素的只读访问
for (auto It = StrArr.CreateConstIterator(); It; ++It)
{
JoinedStr += *It;
JoinedStr += TEXT(" ");
}
排序
Sort
StrArr.Sort(); //数值按元素类型的 运算符< 排序
- 二元谓词提供不同的排序语义
StrArr.Sort([](const FString& A, const FString& B) {
return A.Len() < B.Len();
}); //字符串现在按长度排序
HeapSort
- 无论是否使用二元谓词,均可用于执行堆排序。使用HeapSort函数与否,取决于特定数据与Sort函数相比时的排序效率。与 Sort 一样,HeapSort 也不稳定
StrArr.HeapSort([](const FString& A, const FString& B) {
return A.Len() < B.Len();
});
StableSort
- 作为归并排序实现,用于在排序后保证等值元素的相对顺序
StrArr.StableSort([](const FString& A, const FString& B) {
return A.Len() < B.Len();
});
查询
Num
查询数组保存的元素数量int32 Count = StrArr.Num();
GetData
返回指向数组中元素的指针,该操作直接访问数组内存- 仅在数组存在且未执行更改数组的操作时,此指针方有效。仅 StrPtr 的首个 Num 指数才可被解除引用
FString* StrPtr = StrArr.GetData(); // StrPtr[0] == "!" // StrPtr[1] == "of" // ... // StrPtr[5] == "Tomorrow" // StrPtr[6] - undefined behavior
GetTypeSize
获取元素大小uint32 ElementSize = StrArr.GetTypeSize(); // ElementSize == sizeof(FString)
- 索引运算符
[]
, 返回引用,可用于改变数组中的元素(假定数组不为常量) IsValidIndex
可确定特定索引是否有效StrArr[3] = StrArr[3].ToUpper(); bool bValidM1 = StrArr.IsValidIndex(-1); bool bValid0 = StrArr.IsValidIndex(0); bool bValid5 = StrArr.IsValidIndex(5); bool bValid6 = StrArr.IsValidIndex(6);
Top
返回最后一个元素Last
从数组末端反向索引。索引默认为零FString ElemEnd = StrArr.Last(); FString ElemEnd0 = StrArr.Last(0); FString ElemEnd1 = StrArr.Last(1); FString ElemTop = StrArr.Top();
Contains
数组是否包含特定元素bool bHello = StrArr.Contains(TEXT("Hello")); // bHello == true bool bGoodbye = StrArr.Contains(TEXT("Goodbye")); // bGoodbye == false
ContainsByPredicate
数组是否包含与特定谓词匹配的元素bool bLen5 = StrArr.ContainsByPredicate([](const FString& Str){ //数组是否包含Len为5的元素 return Str.Len() == 5; }); bool bLen6 = StrArr.ContainsByPredicate([](const FString& Str){ //数组是否包含Len为6的元素 return Str.Len() == 6; });
Find
确定元素是否存在并返回其索引,找到的首个元素的索引FindLast
如存在重复元素而找到最末元素的索引- 两个函数均会返回布尔,指出是否已找到元素,同时在找到元素索引时将其写入变量
- 可直接返回元素索引。如不将索引作为显式参数传递,这两个函数便会执行此操作
- 如未找到元素,将返回特殊 INDEX_NONE 值
int32 Index; if (StrArr.Find(TEXT("Hello"), Index)) { // Index == 3 } int32 IndexLast; if (StrArr.FindLast(TEXT("Hello"), IndexLast)) { // IndexLast == 3, because there aren't any duplicates } int32 Index2 = StrArr.Find(TEXT("Hello")); int32 IndexLast2 = StrArr.FindLast(TEXT("Hello")); int32 IndexNone = StrArr.Find(TEXT("None")); // Index2 == 3 // IndexLast2 == 3 // IndexNone == INDEX_NONE
IndexOfByKey
不同元素可与任意对象比较, 即使键类型无法直接转换为元素类型,也可进行搜索- 返回找到的首个元素的索引;如未找到元素,则返回 INDEX_NONE
IndexOfByPredicate
于查找与特定谓词匹配的首个元素的索引,如未找到,同样返回特殊 INDEX_NONE 值:int32 Index = StrArr.IndexOfByKey(TEXT("Hello")); // Index == 3 int32 Index = StrArr.IndexOfByPredicate([](const FString& Str){ return Str.Contains(TEXT("r")); }); // Index == 2
FindByKey
与IndexOfByKey
相似,返回指针FindByPredicate
的使用方式和IndexOfByPredicate
相似,不同点是返回指针而非索引auto* OfPtr = StrArr.FindByKey(TEXT("of"))); // OfPtr == &StrArr[1] auto* ThePtr = StrArr.FindByKey(TEXT("the"))); // ThePtr == nullptr auto* Len5Ptr = StrArr.FindByPredicate([](const FString& Str){ // Len5Ptr == &StrArr[2] return Str.Len() == 5; }); auto* Len6Ptr = StrArr.FindByPredicate([](const FString& Str){ // Len6Ptr == nullptr return Str.Len() == 6; });
FilterByPredicate
获取与特定谓词匹配的元素数组auto Filter = StrArray.FilterByPredicate([](const FString& Str){ return !Str.IsEmpty() && Str[0] < TEXT('M'); });
移除元素
-
remove
用于移除数组中的元素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]
-
RemoveSingle
用于擦除数组中的首个匹配元素ValArr.RemoveSingle(30); // ValArr == [10,5,10,15,25,30]
-
RemoveAt
按照索引移除元素ValArr.RemoveAt(2); // Removes the element at index 2 // ValArr == [10,5,15,25,30]
-
RemoveAll
移除与谓词匹配的元素ValArr.RemoveAll([](int32 Val) { // ValArr == [10,5,25] 移除为3倍数的所有数值 return Val % 3 == 0; });
-
RemoveSwap
、RemoveAtSwap
和RemoveAllSwap
函数减少此开销- 不保证剩余元素的排序,因此可更快地完成任务
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] ValArr2.RemoveAllSwap([](int32 Val) { return Val % 3 == 0; }); // ValArr2 == [1,4,4]
-
Empty
移除数组中所有元素ValArr2.Empty(); // ValArr2 == []
运算符
- 数组是常规数值类型,可使用标准复制构造函数或赋值运算符进行复制
- 由于数组严格拥有其元素,复制数组的操作是深层的,因此新数组将拥有其自身的元素副本
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];
- 运算符
+=
作为Append
函数的替代,对数组进行串联ValArr4 += ValArr3; // ValArr4 == [5,2,3,1,2,3]
MoveTemp
函数可调用移动语义,移动后,源数组必定为空:ValArr3 = MoveTemp(ValArr4); // ValArr3 == [5,2,3,1,2,3] // ValArr4 == []
- 运算符
==
和 运算符!=
可对数组进行比较- 元素的排序很重要:只有元素的顺序和数量相同时,两个数组才被视为相同。元素通过其自身的 运算符== 进行比较
堆
-
TArray 拥有支持二叉堆数据结构的函数。堆是一种二叉树,其中父节点的排序等于或高于其子节点。作为数组实现时,树的根节点位于元素0,索引N处节点的左右子节点的指数分别为2N+1和2N+2。子节点彼此间不存在特定排序。
-
Heapify
函数可将现有数组转换为堆- 此会重载为是否接受谓词,无谓词的版本将使用元素类型的 运算符< 确定排序
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]
- 树中的节点按堆化数组中元素的排序从左至右、从上至下读取。注意:数组在转换为堆后无需排序。排序数组也是有效堆,但堆结构的定义较为宽松,同一组元素可存在多个有效堆。
-
HeapPush
函数可将新元素添加到堆,对其他节点进行重新排序,以对堆进行维护:heapArr.HeapPush(4); // HeapArr == [1,2,4,3,4,5,8,10,7,9,6]
-
HeapPop
和HeapPopDiscard
函数用于移除堆的顶部节点- 这两个函数的区别在于前者引用元素的类型来返回顶部元素的副本,而后者只是简单地移除顶部节点,不进行任何形式的返回
int32 TopNode; HeapArr.HeapPop(TopNode); // TopNode == 1 HeapArr == [2,3,4,6,4,5,8,10,7,9]
-
HeapRemoveAt
将删除数组中给定索引处的元素,然后重新排列元素,对堆进行维护HeapArr.HeapRemoveAt(1); // HeapArr == [2,4,4,6,9,5,8,10,7]
-
HeapTop
检查堆的顶部节点,无需变更数组
int32 Top = HeapArr.HeapTop(); // Top == 2
Heapify
调用、其他堆操作或手动将数组操作到堆中之后),才应调用HeapPush
、HeapPop
、HeapPopDiscard
和HeapRemoveAt
slack
- 为避免每次添加元素时重新分配内存,分配器提供的内存通常会超过必要内存,使之后调用 Add 时不会因重新分配内存而降低性能
- 同样,删除元素通常不会释放内存.此操作会使数组拥有Slack元素,也就是当前未使用的有效预分配元素储存槽
- 数组中存储的元素量与数组使用分配内存可存储的元素数量间的差值即为数组中的Slack量
- 由于默认构建的数组不分配内存,Slack初始为零
GetSlack
函数可找出数组中的Slack量Max
函数可获取容器重新分配前数组可保存的最大元素数量GetSlack
=Max
-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 SlackArray.Add(2); SlackArray.Add(3); SlackArray.Add(4); SlackArray.Add(5); // SlackArray.GetSlack() == 17 // SlackArray.Num() == 5 // SlackArray.Max() == 22
- 虽然无需管理Slack,但可管理Slack对数组进行优化,以满足需求
- 例如,如需要向数组添加大约100个新元素,则可在添加前确保拥有可至少存储100个新元素的Slack,以便添加新元素时无需分配内存
- 上文所述的
Empty
函数接受可选Slack参数: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); SlackArray.Add(3); // SlackArray.GetSlack() == 0 // SlackArray.Num() == 3 // SlackArray.Max() == 3
Reset
函数与Empty
函数类似,不同之处是若当前内存分配已提供请求的Slack,该函数将不释放内存。但若请求的Slack较大,其将分配更多内存SlackArray.Reset(0); //SlackArray.GetSlack() == 3 SlackArray.Num() == 0 SlackArray.Max() == 3 SlackArray.Reset(10); //SlackArray.GetSlack() == 10 SlackArray.Num() == 0 SlackArray.Max() == 10
Shrink
函数可移除所有Slack- 将把内存分配调整为保存当前元素所需的最小内存
- Shrink 不会对数组中的元素产生影响
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 // SlackArray.Max() == 4