【UE4从零开始 010】容器之TArray
TArray 是虚幻引擎中最常用的容器类。其设计决定了它速度较快、内存消耗较少、安全性高。1、创建TArray<int32> IntArray;这会创建一个空数组,可以保存整数序列。2、填充数据Init使用 Init 函数,用大量元素副本填入TArray。如:IntArray.Init(10, 5);// IntArray == [10,10,10,10,10]...
TArray 是虚幻引擎中最常用的容器类。其设计决定了它速度较快、内存消耗较少、安全性高。
1、创建
TArray<int32> IntArray;
这会创建一个空数组,可以保存整数序列。
2、填充数据
Init
使用 Init 函数,用大量元素副本填入TArray。如:
IntArray.Init(10, 5);
// IntArray == [10,10,10,10,10]
向IntArray中填充 5 个 10 。
Add 和 Emplace
Add 和 Emplace最终效果一样,但存在细微不同:
TArray<FString> StrArr;
StrArr.Add(TEXT("Hello"));
StrArr.Emplace(TEXT("World"));
// StrArr == ["Hello","World"]
Add 将创建一个临时 FString,然后将临时内容移至容器内的新FString中;
Emplace 将会直接创建新FString。
虽然最终结果一样,但 Emplace 可避免创建不必要的临时变量,效率比 Add 高,但 Add 可读性强。因此可以在浅显类型上使用Add,在其他类型上使用Emplace。
Append
使用 Append 可以从另一个TArray 或 常规数组 一次性添加多个元素。
FString Arr[] = { TEXT("of"), TEXT("Tomorrow") };
StrArr.Append(Arr, ARRAY_COUNT(Arr));
// StrArr == ["Hello","World","of","Tomorrow"]
AddUnique
添加一个TArray中不存在的元素,如果已经存在则不会添加。使用运算符 == 检查是否相等。
StrArr.AddUnique(TEXT("!"));
// StrArr == ["Hello","World","of","Tomorrow","!"]
StrArr.AddUnique(TEXT("!"));
// StrArr is unchanged as "!" is already an element
Insert
在指定索引处插入元素。
StrArr.Insert(TEXT("Brave"), 1);
// StrArr == ["Hello","Brave","World","of","Tomorrow","!"]
SetNum
设置TArray元素数量,如果大于当前数量,则使用元素类型的默认构造函数创建新元素,如果数量小于当前数量,则将移除多余的元素。
StrArr.SetNum(8);
// StrArr == ["Hello","Brave","World","of","Tomorrow","!", "", ""]
StrArr.SetNum(6);
// StrArr == ["Hello","Brave","World","of","Tomorrow","!"]
3、遍历
有多种方法可遍历TArray,推荐使用C++的 ranged-for’:
FString JoinedStr;
for (auto& Str : StrArr)
{
JoinedStr += Str;
JoinedStr += TEXT(" ");
}
// JoinedStr == "Hello Brave World of Tomorrow ! "
也可使用基于索引的for循环:
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(" ");
}
以上3中遍历效果相同。
4、排序
Sort
StrArr.Sort();
// StrArr == ["!","Brave","Hello","of","Tomorrow","World"]
这里是使用元素类型的 运算符 “<” 来进行比较排序。
Sort 函数还可以传入一个 Lambda表达式 来自定义比较规则。如:
StrArr.Sort([](const FString& A, const FString& B) {
return A.Len() < B.Len();
});
// StrArr == ["!","of","Hello","Brave","World","Tomorrow"]
此时是按字符串长度进行排序。注意:等值元素(“Hello”、“Brave”和“World” 长度相同)的相对排序无法保证。
HeapSort
StrArr.HeapSort([](const FString& A, const FString& B) {
return A.Len() < B.Len();
});
// StrArr == ["!","of","Hello","Brave","World","Tomorrow"]
HeapSort,堆排序。是否使用 HeapSort 取决于特定数据以及与 Sort 相比排序的效率。与 Sort 一样,HeapSort 也无法保证等值元素的相对排序。
StableSort
StrArr.StableSort([](const FString& A, const FString& B) {
return A.Len() < B.Len();
});
// StrArr == ["!","of","Brave","Hello","World","Tomorrow"]
与 Sort 和 HeapSort 相比,StableSort 可以保证等值元素的相对排序。StableSort 会保持等值元素之前的顺序。
以上3中排序方式可以自己运行一下代码查看效果与区别。
5、查询相关
Num 函数可以获取TArray保存的元素数量:
int32 Count = StrArr.Num();
// Count == 6
可以使用 GetData 函数获取TArray的指针,通过索引访问TArray中的元素,如同普通的数组:
FString* StrPtr = StrArr.GetData();
// StrPtr[0] == "!"
// StrPtr[1] == "of"
// StrPtr[2] == "Brave"
// StrPtr[3] == "Hello"
// StrPtr[4] == "World"
// StrPtr[5] == "Tomorrow"
// StrPtr[6] - undefined behavior
如果容器为常量,则返回的指针也是常量。
也可以直接使用索引访问TArray中的元素,如:
FString Elem2 = StrArr[2];
// Elem2 == "Brave"
当索引小于零或大于等于Num()时,为无效索引,会引起运行时错误。可以使用 IsValidIndex 函数来确定索引是否有效:
bool bValidM1 = StrArr.IsValidIndex(-1);
bool bValid0 = StrArr.IsValidIndex(0);
bool bValid6 = StrArr.IsValidIndex(6);
// bValidM1 == false
// bValid0 == true
// bValid6 == false
还可以使用 Last 函数从TArray末端反向索引:
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"
StrArr.Top() 等同于 StrArr.Last() 等同于 StrArr.Last(0)
判断TArray是否包含某个特定元素,使用 Contains:
bool bHello = StrArr.Contains(TEXT("Hello"));
bool bGoodbye = StrArr.Contains(TEXT("Goodbye"));
// bHello == true
// bGoodbye == false
还可以使用 Lambda表达式 来判断是否包含 具有某些特征的元素,使用 ContainsByPredicate:
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 == false
使用 Find 查找某个元素是否存在并返回其索引:
int Index = StrArr.Find(TEXT("Hello"));
int IndexLast = StrArr.FindLast(TEXT("Hello"));
int IndexNone = StrArr.Find(TEXT("NONE"));
// Index2 == 3
// IndexLast2 == 3
// IndexNone == INDEX_NONE
Find 和 FindLast 会返回找到的 第一个 元素的索引,若未找到则返回 INDEX_NONE,FindLast从后往前查找。
通过 FInd 查找时,参数将被转换为元素类型(此例中转为FString)。而 IndexOfByKey 与 Find 工作方式类似,但 IndexOfByKey 直接使用 Key 进行对比,以便在Key类型无法直接转换为元素类型时能照常进行查找。
IndexOfByKey 可用于任意具有 运算符“==” 的类型。
int32 IndexKey = StrArr.IndexOfByKey(TEXT("Hello"));
int32 IndexKeyNone = StrArr.IndexOfByKey(TEXT("NONE"));
// IndexKey == 3
// IndexKeyNone == INDEX_NONE
IndexOfByPredicate 可以使用 Lambda表达式 来查找 具有某些特征的元素。
int32 IndexKeyPre = StrArr.IndexOfByPredicate([](const FString& Str) {
return Str.Contains(TEXT("r"));
});
// Index == 2
使用 FindByKey 可以返回找到的元素的 指针,而不是返回索引,若未找到则返回 nullptr。FindByKey 与 IndexOfByKey 相似,可以将元素与任意对象进行对比。
auto* OfPtr = StrArr.FindByKey(TEXT("of"));
auto* ThePtr = StrArr.FindByKey(TEXT("the"));
// OfPtr == &StrArr[1]
// ThePtr == nullptr
与 IndexOfByKey 一样,FindByKey 也具有可以使用 Lambda表达式 的函数 FindByPredicate。
而 FilterByPredicate 将会返回一个 具有某个特征的元素数组。
auto Filter = StrArr.FilterByPredicate([](const FString& Str) {
return !Str.IsEmpty() && Str[0] < TEXT('M');
});
// Filter == ["!","Brave","Hello"]
6、移除
Remove
StrArr.Remove(TEXT("Hello"));
// StrArr == ["!","of","Brave","World","Tomorrow"]
StrArr.Remove(TEXT("goodbye"));
// StrArr is unchanged, as it doesn't contain "goodbye"
Remove 会移除与传入元素相等的 所有 元素, RemoveSingle 则只会移除TArray中与传入元素相等的最靠前的一个元素。
RemoveAt
StrArr.RemoveAt(1);
// StrArr == ["!","Brave","World","Tomorrow"]
RemoveAt 按元素索引移除元素。索引必须存在,否则会出现运行时错误。还可以使用 RemoveAll 以 Lambda表达式 的方式来移除具有某些特征的所有元素。
Pop
Pop 将移除TArray的最后一个元素:
StrArr.Pop();
// StrArr == ["!","Brave","World"]
以上这些情况,当元素被移除时,其后的元素会向前移,移动过程存在开销。若不介意剩余元素的排序,可使用 RemoveSwap、RemoveAtSwap 和 RemoveAllSwap 来减少开销。使用 Empty 可以移除所有的元素。
7、运算符
TArray是常规数值类型,可以通过标准复制构造函数或赋值运算符被复制。因其严格拥有其元素,所有TArray复制是深度复制,所有新的TArray将拥有其自身的元素副本。
TArray<int32> ValArr;
ValArr.Add(1);
ValArr.Add(2);
ValArr.Add(3);
auto ValArr1 = ValArr;
// ValArr1 == [1,2,3];
ValArr1[0] = 5;
// ValArr == [1,2,3];
// ValArr1 == [5,2,3];
而 运算符"+=" 可以替代 Append 函数来串联TArray。
ValArr1 += ValArr;
// ValArr1 == [5,2,3,1,2,3]
还可以使用 MoveTemp 来移动TArray,移动后,源数组将为空。
ValArr = MoveTemp(ValArr1);
// ValArr == [5,2,3,1,2,3]
// ValArr1 == []
还可以使用 “==” 和 “!=” 对TArray进行比较,元素数量和排序均相同的两个TArray才可能相等。元素通过其自身的 运算符 “==” 进行对比。
TArray<FString> FlavorArr1;
FlavorArr1.Emplace(TEXT("Chocolate"));
FlavorArr1.Emplace(TEXT("Vanilla"));
// FlavorArr1 == ["Chocolate","Vanilla"]
auto FlavorArr2 = FlavorArr1;
// FlavorArr2 == ["Chocolate","Vanilla"]
bool bComparison1 = FlavorArr1 == FlavorArr2;
// bComparison1 == true
for (auto& Str : FlavorArr2)
{
Str = Str.ToUpper();
}
// FlavorArr2 == ["CHOCOLATE","VANILLA"]
bool bComparison2 = FlavorArr1 == FlavorArr2;
// bComparison2 == true, because FString comparison ignores case
Exchange(FlavorArr2[0], FlavorArr2[1]);
// FlavorArr2 == ["VANILLA","CHOCOLATE"]
bool bComparison3 = FlavorArr1 == FlavorArr2;
// bComparison3 == false, because the order has changed
8、Slack
由于TArray的长度是可变的,为避免每次添加元素时需要重新分配内存,分配器通常会提供比需求更多的内存,从而使之后调用 Add 时不会因为重新分配内存而出现性能损失。同样,删除元素时通常也不会释放内存。在下次重新分配内存前剩余的可添加的元素数量即称为 slack。
默认构建的TArray不分配内存,即slack初始为零。使用 GetSlack 可获取TArray的 slack 。使用 Max 可获取重新分配内存之前 TArray 可保存的最大元素数量。 即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个元素到TArray的话,我们可以确保在添加元素前使其拥有100个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。
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
9、测试代码
TArray<int32> IntArray;
IntArray.Init(10, 5);
// IntArray == [10,10,10,10,10]
TArray<FString> StrArr;
StrArr.Add(TEXT("Hello"));
StrArr.Emplace(TEXT("World"));
// StrArr == ["Hello", "World"]
FString Arr[] = { TEXT("of"), TEXT("Tomorrow") };
StrArr.Append(Arr, ARRAY_COUNT(Arr));
// StrArr == ["Hello","World","of","Tomorrow"]
StrArr.AddUnique(TEXT("!"));
// StrArr == ["Hello","World","of","Tomorrow","!"]
StrArr.AddUnique(TEXT("!"));
// StrArr is unchanged as "!" is already an element
StrArr.Insert(TEXT("Brave"), 1);
// StrArr == ["Hello","Brave","World","of","Tomorrow","!"]
StrArr.SetNum(8);
// StrArr == ["Hello","Brave","World","of","Tomorrow","!", "", ""]
StrArr.SetNum(6);
// StrArr == ["Hello","Brave","World","of","Tomorrow","!"]
FString JoinedStr;
for (auto& Str : StrArr)
{
JoinedStr += Str;
JoinedStr += TEXT(" ");
}
// JoinedStr == "Hello Brave World of Tomorrow ! "
for (int32 Index = 0; Index != StrArr.Num(); ++Index)
{
JoinedStr += StrArr[Index];
JoinedStr += TEXT(" ");
}
for (auto It = StrArr.CreateConstIterator(); It; ++It)
{
JoinedStr += *It;
JoinedStr += TEXT(" ");
}
UE_LOG(LogClass, Log, TEXT("*******************************************************"));
for (auto& Str : StrArr)
{
UE_LOG(LogClass, Log, TEXT("%s"), *Str);
}
UE_LOG(LogClass, Log, TEXT("*******"));
StrArr.Sort();
// StrArr == ["!","Brave","Hello","of","Tomorrow","World"]
StrArr.Sort([](const FString& A, const FString& B) {
return A.Len() < B.Len();
});
// StrArr == ["!","of","Hello","Brave","World","Tomorrow"]
StrArr.HeapSort([](const FString& A, const FString& B) {
return A.Len() < B.Len();
});
// StrArr == ["!","of","Hello","Brave","World","Tomorrow"]
StrArr.StableSort([](const FString& A, const FString& B) {
return A.Len() < B.Len();
});
// StrArr == ["!","of","Brave","Hello","World","Tomorrow"]
for (auto& Str : StrArr)
{
UE_LOG(LogClass, Log, TEXT("%s"), *Str);
}
int32 Count = StrArr.Num();
// Count == 6
FString* StrPtr = StrArr.GetData();
// StrPtr[0] == "!"
// StrPtr[1] == "of"
// StrPtr[2] == "Brave"
// StrPtr[3] == "Hello"
// StrPtr[4] == "World"
// StrPtr[5] == "Tomorrow"
// StrPtr[6] - undefined behavior
FString Elem2 = StrArr[2];
// Elem2 == "Brave"
UE_LOG(LogClass, Log, TEXT("======="));
for (int i = 0; i < 6; i++)
{
UE_LOG(LogClass, Log, TEXT("%s"), *StrPtr[i]);
UE_LOG(LogClass, Log, TEXT("%s"), *StrArr[i]);
}
bool bValidM1 = StrArr.IsValidIndex(-1);
bool bValid0 = StrArr.IsValidIndex(0);
bool bValid6 = StrArr.IsValidIndex(6);
// bValidM1 == false
// bValid0 == true
// bValid6 == false
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"
bool bHello = StrArr.Contains(TEXT("Hello"));
bool bGoodbye = StrArr.Contains(TEXT("Goodbye"));
// bHello == true
// bGoodbye == false
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 == false
int32 Index = StrArr.Find(TEXT("Hello"));
int32 IndexLast = StrArr.FindLast(TEXT("Hello"));
int32 IndexNone = StrArr.Find(TEXT("NONE"));
// Index2 == 3
// IndexLast2 == 3
// IndexNone == INDEX_NONE
int32 IndexKey = StrArr.IndexOfByKey(TEXT("Hello"));
int32 IndexKeyNone = StrArr.IndexOfByKey(TEXT("NONE"));
// IndexKey == 3
// IndexKeyNone == INDEX_NONE
int32 IndexKeyPre = StrArr.IndexOfByPredicate([](const FString& Str) {
return Str.Contains(TEXT("r"));
});
// Index == 2
auto* OfPtr = StrArr.FindByKey(TEXT("of"));
auto* ThePtr = StrArr.FindByKey(TEXT("the"));
// OfPtr == &StrArr[1]
// ThePtr == nullptr
auto Filter = StrArr.FilterByPredicate([](const FString& Str) {
return !Str.IsEmpty() && Str[0] < TEXT('M');
});
// Filter == ["!","Brave","Hello"]
UE_LOG(LogClass, Log, TEXT("+++++++"));
for (auto& Str : Filter)
{
UE_LOG(LogClass, Log, TEXT("%s"), *Str);
}
StrArr.Remove(TEXT("Hello"));
// StrArr == ["!","of","Brave","World","Tomorrow"]
StrArr.Remove(TEXT("goodbye"));
// StrArr is unchanged, as it doesn't contain "goodbye"
StrArr.RemoveAt(1);
// StrArr == ["!","Brave","World","Tomorrow"]
StrArr.Pop();
// StrArr == ["!","Brave","World"]
TArray<int32> ValArr;
ValArr.Add(1);
ValArr.Add(2);
ValArr.Add(3);
auto ValArr1 = ValArr;
// ValArr1 == [1,2,3];
ValArr1[0] = 5;
// ValArr == [1,2,3];
// ValArr1 == [5,2,3];
ValArr1 += ValArr;
// ValArr1 == [5,2,3,1,2,3]
ValArr = MoveTemp(ValArr1);
// ValArr == [5,2,3,1,2,3]
// ValArr1 == []
TArray<FString> FlavorArr1;
FlavorArr1.Emplace(TEXT("Chocolate"));
FlavorArr1.Emplace(TEXT("Vanilla"));
// FlavorArr1 == ["Chocolate","Vanilla"]
auto FlavorArr2 = FlavorArr1;
// FlavorArr2 == ["Chocolate","Vanilla"]
bool bComparison1 = FlavorArr1 == FlavorArr2;
// bComparison1 == true
for (auto& Str : FlavorArr2)
{
Str = Str.ToUpper();
}
// FlavorArr2 == ["CHOCOLATE","VANILLA"]
bool bComparison2 = FlavorArr1 == FlavorArr2;
// bComparison2 == true, because FString comparison ignores case
Exchange(FlavorArr2[0], FlavorArr2[1]);
// FlavorArr2 == ["VANILLA","CHOCOLATE"]
bool bComparison3 = FlavorArr1 == FlavorArr2;
// bComparison3 == false, because the order has changed
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
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
SlackArray.Reset(0);
// SlackArray.GetSlack() == 3
// SlackArray.Num() == 0
// SlackArray.Max() == 3
SlackArray.Reset(10);
// SlackArray.GetSlack() == 10
// SlackArray.Num() == 0
// SlackArray.Max() == 10
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
更多推荐
所有评论(0)