TArray 是虚幻引擎中最常用的容器类。其设计决定了它速度较快、内存消耗较少、安全性高。

1、创建

TArray<int32> IntArray;

这会创建一个空数组,可以保存整数序列。

2、填充数据

Init

使用 Init 函数,用大量元素副本填入TArray。如:

IntArray.Init(10, 5);
// IntArray == [10,10,10,10,10]

向IntArray中填充 510

AddEmplace

AddEmplace最终效果一样,但存在细微不同:

	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"]

SortHeapSort 相比,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

FindFindLast 会返回找到的 第一个 元素的索引,若未找到则返回 INDEX_NONE,FindLast从后往前查找。

通过 FInd 查找时,参数将被转换为元素类型(此例中转为FString)。而 IndexOfByKeyFind 工作方式类似,但 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 可以返回找到的元素的 指针,而不是返回索引,若未找到则返回 nullptrFindByKeyIndexOfByKey 相似,可以将元素与任意对象进行对比。

	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 按元素索引移除元素。索引必须存在,否则会出现运行时错误。还可以使用 RemoveAllLambda表达式 的方式来移除具有某些特征的所有元素。

Pop

Pop 将移除TArray的最后一个元素:

	StrArr.Pop();
	// StrArr == ["!","Brave","World"]

以上这些情况,当元素被移除时,其后的元素会向前移,移动过程存在开销。若不介意剩余元素的排序,可使用 RemoveSwapRemoveAtSwapRemoveAllSwap 来减少开销。使用 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
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐