TArray
是UE4中最常用的容器类。其速度快、内存消耗小、安全性高。TArray
类型由两大属性定义:元素类型和可选分配器。
元素类型是存储在数组中的对象类型。TArray
被称为同质容器。换言之,其所有元素均完全为相同类型。单个 TArray
中不能存储不同类型的元素。
分配器常被省略,默认为最常用的分配器。其定义对象在内存中的排列方式;以及数组如何进行扩展,以容纳更多的元素。若默认行为不符合要求,可选取多种不同的分配器,或自行编写。此部分将稍后讨论。
Tarray
为数值类型。意味其与其他内置类型(如 int32
或 浮点
)的处理方式相同。其设计时未考虑扩展问题,因此建议在实际操作中勿使用 新建(new)
和 删除(delete)
创建或销毁 TArray
实例。元素也为数值类型,为容器所拥有。TArray
被销毁时其中的元素也将被销毁。若在另一TArray中创建TArray变量,其元素将复制到新变量中,且不会共享状态。
创建和填充数组
创建一个Actor创建该函数
void InitIntArray()
{
TArray<int32> IntArray1;
TArray<int32> IntArray2;
int32 ArrayNum = IntArray1.Num();
int32 ArraySize = IntAllocatedSize();
//初始化为数组大小为五,值为10
IntArray1.Init(10,5);
IntArray2 = {1,2,3,4,5};
ArrayNum = IntArray1.Num();
ArraySize = IntArray1.GetAllocatedSized();
}
Add和Emplace的区别
新元素添加到数组时,数组的分配器将根据需要分配内存。当前数组大小超出时,默认分配器将添加足够的内存,用于存储多个新元素。Add
和 Emplace
函数的多数效果相同,细微区别在于:
Add
(或 Push
)将元素类型的实例复制(或移动)到数组中。
Emplace
使用给定参数构建元素类型的新实例。
因此在 TArray<FString>
中,Add
将用字符串文字创建临时 FString
,然后将该临时 FString
的内容移至容器内的新 FString
中;而 Emplace
将用字符串文字直接新建 FString
。最终结果相同,但 Emplace
可避免创建临时文件。对于 FString
等非浅显数值类型而言,临时文件通常有害无益。
总体而言,Emplace
优于 Add
,因此其可避免在调用点创建无需临时变量,并将此类变量复制或移动到容器中。根据经验,可将 Add
用于浅显类型,将 Emplace
用于其他类型。Emplace
的效率始终高于 Add
,但 Add
的可读性可能更好。
void AddStrArray()
{
TArray<FString> StrArr;
StrArr.Add(TEXT("Hello"));
StrArr.Emplace(TEXT("World"));
StrArr.Add(TEXT("My"));
StrArr.Add(TEXT("City"));
StrArr.Add(TEXT("Shanxi"));
StrArr.Add(TEXT("Well"));
}
定义一个结构体
UStruct()
struct AddStructInfo
{
GENERATED_BODY()
public:
AddStructInfo()
{ UE_LOG(LogTemp,Warning,TEXT("我被初始化了0")); }
AddStructInfo(int32 InHealth) : Healty(InHealth)
{ UE_LOG(LogTemp,Warning,TEXT("我被初始化了1")); }
~AddStructInfo()
{ UE_LOG(LogTemp,Warning,TEXT("我被释放了")); }
int32 Health = 100;
};
在.h文件中声明该属性
public:
TArray<AddStructInfo> StructArr;
在这里创建两个方法用来调试,将该两个方法暴露在蓝图中进行调试
void AddStructArray()
{
StructArray.Add(150);
}
void EmplaceStructArray()
{
StructArray.Emplace(200);
}
我们可以观察到当我们使用Add方法时 我们观察日志可以看到
LogTemp,Warning,我被初始化了1
LogTemp,Warning,我被释放了
当我们使用Emplace方法时我们观察日志可以看到
LogTemp,Warning,我被初始化了1
当我们游戏结束时
LogTemp,Warning,我被释放了
由此可以看出Add需要一个临时变量将元素类型的实例复制(或移动)到数组中,然后释放临时对象。
而Emplace则是直接构建一个新的对象,只有当actor消亡的时候他才会释放
其他添加元素的方式
对Append的测试
写出该方法,同时将该方法暴露在蓝图中
void AppendStrArray()
{
FString Arr[] = { TEXT("of"),TEXT("Tomorrow") };
StrArr.Append()
StrArr.Add(TEXT("Hello"));
StrArr.Emplace(TEXT("World"));
StrArr.Append(Arr, UE_ARRAY_COUNT(Arr));
}
通过打算点可以测试出
对AddUnique
使用该方法不出现两次相同的元素
void AddUniqueString()
{
TArry<Fstring> StrArr = AppendStrArray();
StrArr.AddUnique(TEXT("!"));
StrArr.AddUnique(TEXT("!"));
}
dda
该函数执行完之后只有一个感叹号
Insert方法的测试
void InsertString()
{
TArray<FString> StrArr = { "Hello","World","of","Tomorrow","!"};
StrArr.Insert(TEXT("Brave"),1);
}
打断点测试结果如下
SetNum
void SetStringNum()
{
TArray<FString> StrArr = { "Hello","World","of","Tomorrow","!"};
StrArr.SetNum(10);
StrArr.SetNum(5);
}
结构体唯一性的判断
数组迭代实战
使用C++的范围(ranged-for)功能
UFUNTION(BlueprintCallable)
void LoopArray();
void LoopArray()
{
FString JoinedStr1;
FString JoinedStr2;
TArray<FString> StrArr = { "Hello","World","of","Tomorrow","!" };
for(auto& Str : StrArr)
{
JoinedStr1 += Str;
JOinedStr1 += TEXT(" ");
}
for(auto Str : StrArr)
{
JoinedStr2 += Str;
JoinedStr2 += TEXT(" ");
}
}
通过打断点测试可以测出
基于索引的常规迭代
void LoopArray2()
{
FString JoinedStr;
TArray<FString> StrArr = { "Hello","World","of","Tomorrow","!"};
for( int32 Index = 0; Index != StrArr.Num(); ++Index)
{
JoinedStr += StrArr[Index];
JoinedStr += TEXT(" ");
}
}
通过数组迭代器类型控制迭代
void LoopArray3()
{
FString JoinedStr;
TArray<FString> StrArr = { "Hello","World","of","Tomorrow","!"};
for(auto It = StrArr.CreateConstInterator(); It; ++It)
{
JoinedStr += *It;
JoinedStr += TEXT(" ");
}
}
移除数组中的元素
当正序移除其中元素时,TArray数组内部会进行重新排序导致移除的数据出错
这是第一种移除方式
void LoopArray_Remove()
{
FString JoinedStr;
TArray<FString> StrArr = {"Hello"," World","of","Tomorrow","!"}
for (int32 Index = StrArr.Num()-1; Index >= 0 ; --Index)
{
if(TEXT("of") == StrArr[Index])
{
if(TEXT("of") == StrArr[Index])
{
StrArr.RemoveAt("Index");
}
else
{
JoinedStr = StrArr[Index] + JoinedStr;
JoinStr += StrArr[Index];
JoinedStr += TEXT(" ");
}
}
}
}
这是第二种移除方式
void LoopArray_Remove()
{
TArray<FString> StrArr = {"Hello","My"," World","of","Tomorrow","!"}
TArray<int32> RemovedIndexArray
for (int32 Index = StrArr.Num()-1; Index >= 0 ; --Index)
{
if(TEXT("of") == StrArr[Index])
{
RmovedIndexArray.Add(Index);
}
if(TEXT("of") == StrArr[Index])
{
RemovedIndexArray.Add(Index);
}
}
for(int32 RemoveLoopIndex = 0; RemoveLoopIndex != RemovedIndexArray; ++RemovedLoopIndex)
{
StrArr.RemoveAt(RemovedIndexArray[RemovedLoopIndex])
}
}
//注意一定要分帧执行
//WebSocket通信过程去创建新webSocket
常见排序
Sort函数
在此,数值按元素类型的 运算符<
排序。在FString中,此为忽略大小写的词典编纂比较。
void SortArray()
{
TArray<FString> StrArr = { "aa","ab","AA","ab","ABC","BB","VE","CCCCC","AD","DDDDDA","AA" }
StrArr.Sort();
}
字符串现在按长度排序。注意:与之前相比,数组中三个长度相同的字符串"Hello"、"Brave"和"World"的相对排序发生了变化。这是因为 Sort
不稳定,等值元素(因为断言只比较长度,所以此处字符串为等值)的相对排序无法保证。Sort
作为quicksort实现。
HeapSort
函数
无论是否使用二元谓词,均可用于执行堆排序。使用HeapSort函数与否,取决于特定数据与Sort函数相比时的排序效率。与 Sort
一样,HeapSort
也不稳定。若在上述范例中使用 HeapSort
而非 Sort
,结果将如下所示(此例中结果相同):
void HeapArray()
{
TArray<FString> StrArr = { "aa","ab","AA","ab","ABC","BB","VE","CCCCC","AD","DDDDDA","AA" }
StrArr.Sort();
}
StableSort
函数
用于在排序后保证等值元素的相对顺序。
void StableArray()
{
TArray<FString> StrArr = { "aa","ab","AA","ab","ABC","BB","VE","CCCCC","AD","DDDDDA","AA" }
Stable.Sort();
}
二元谓词排序实际应用
void SortArray_Sort
{
int32 OriginNum = 100;
auto Lamba = [OriginNum](int32 InNum,int32 InAddNum)
{
int32 Max = InNum + InAddNum;
Max += 100;
return Max;
};
int32 CaculatedNum1 = LambdaMethod(10,2);
int32 CaculatedNum2 = CalculateMethod(10,2);
}
int32 CalculateMethod(int32 InNum,int InAddNum)
{
int32 Max = InNum + InAddNum;
Max += TempNum;
return Max;
}
void SortArray_Sort2
{
TArray<FString> StrArr = { "CCCCC","BB","aa","ab","ABC","VE","AA","AD""AB","DDDDDA","AA" };
StrArr.HeapSort([](const FString& A,const FString& B)
{
return A.Len < B.Len;
});
}
void SortArray_Sort2
{
TArray<FString> StrArr = { "CCCCC","BB","aa","ab","ABC","VE","AA","AD""AB","DDDDDA","AA" };
StrArr.StableSort(const FString& A,const FString& B)
{
return A.Len < B.Len;
}
}
字符串现在按长度排序。注意:与之前相比,数组中三个长度相同的字符串"Hello"、"Brave"和"World"的相对排序发生了变化。这是因为 Sort
不稳定,等值元素(因为断言只比较长度,所以此处字符串为等值)的相对排序无法保证。Sort
作为quicksort实现。
HeapSort
函数,无论是否使用二元谓词,均可用于执行堆排序。使用HeapSort函数与否,取决于特定数据与Sort函数相比时的排序效率。与 Sort
一样,HeapSort
也不稳定。
记录一个常用的方法
可以用在物品栏当中
UStruct()
struct SortStructInfo
{
GENERATED_BODY()
public:
SortStructInfo() {}
SortStructInfo(int32 InID,int32 InMoney)
: ID(InID),Money(InMoney) { }
int32 ID = 0;
int32 Money = 0;
bool operator == (const SortStructInfo& Other) const
{ return ID < Other.ID ? true : false; }
};
void SortStructArray_StableSort()
{
TArray<SortStructInfo> MyTeams1;
MyTeams1.Add( { 99,2 } );
MyTeams1.Add( { 1,10} );
MyTeams1.Add( { 66,3} );
MyTeams1.Add( { 88,60} );
TArray<SortStructInfo> MyTeams2;
MyTeams2.Add( { 99,2 } );
MyTeams2.Add( { 1,10} );
MyTeams2.Add( { 66,3} );
MyTeams2.Add( { 88,60} );
MyTeams1.StableSort();
MyTeams2.StableSort([](const SortStructInfo&A,const SortStructInfo& B)->bool{
return A.Money< B.Money ? true : false;
} );
}
数组查询演示
在引擎中进行调试,可以直观观察出。
void FindArray()
{
TArray<FString> StrArr = { "Hello","World","of","Tomorrow","!" };
int32 ArraySize = StrArr.Num();
FString* StrPtr = StrArr.GetData();
bool bP0 = StrPtr[0] == "Hello";
bool bP1 = StrPtr[1] == "World";
bool bP2 = StrPtr[2] == "of";
bool bP2 = StrPtr[3] == "of";
//!
FString MyTemp1 = *(StrPtr + 4)
//Hello 4
FString MyTemp2 = *StrPtr + TEXT("4");
//false
bool bP2 = *(StrPtr+4) == "of";
StrPtr[5];
//越界
StrArr[5];
}
void FindArray_Change()
{
TArray<FString> StrArr = { "Hello","World","of","Tomorrow","!" };
StrArr.Append(StrArr2);
StrArr.Append(StrArr2);
StrArr.Append(StrArr2);
StrArr.Append(StrArr2);
FString* StrPtr = StrArr.GetData();
bool bP0 = StrPtr[0] == "Hello";
bool bP1 = StrPtr[1] == "World";
bool bP2 = StrPtr[2] == "of";
bool bP2 = StrPtr[3] == "of";
bool bPZ0 = StrArr[0] == "Hello";
bool bPZ1 = StrArr[1] == "World";
bool bPZ2 = StrArr[2] == "of";
bool bPZ2 = StrArr[3] == "of";
}
void FindArray_ElementSize()
{
TArray<FString> StrArr = { "Hello","World","Of","Tomorrow","!" };
uint32 ElementSize1 = StrArr.GetTypeSize();
uint32 ElementSize2 = sizeof(FString);
TArray<int32> IntArr = { 2,3,4,5,6 };
uint32 ElementSize3 = IntArr.GetTypeSize();
TArray<uint8> Uint8Arr = { 2,3,4,5,6 };
uint32 ElementSize4 = Uint8Arr.GetTypeSize();
}
数组索引演示
void FindArray_Index()
{
TArray<FString> StrArr = { "Hello","World","of","Tomorrow","!" };
FString Elem1 = StrArr[1];
Elem1 += TEXT("10");
FString& Elem1_X = StrArr[1];
Elem1_X += TEXT("10");
}
void IndexArray_IsValid()
{
TArray<FString> StrArr = { "Hello","World","of","Tomorrow","!" };
bool bValidM1 = StrArr.IsValidIndex(-1);
bool bValied0 = StrArr.IsValidIndex(0);
bool bValied1 = StrArr.IsValidIndex(1);
bool bValied2 = StrArr.IsValidIndex(2);
bool bValied3 = StrArr.IsValidIndex(3);
bool bValied4 = StrArr.IsValidIndex(4);
bool bValied5 = StrArr.IsValidIndex(5);
bool bValied6 = StrArr.IsValidIndex(6);
}
void IndexArray_Latest()
{
TArray<FString> StrArr = { "Hello","World","of","Tomorrow","!" };
FString ElemEnd = StrArr.Last();
FString ElemEnd0 = StrArr.Last(0);
FString ElemEnd1 = StrArr.Last(1);
FString ElemTop = StrArr.Top();
}
void IndexArray_Upper
{
TArray<FString> StrArr = { "Hello","World","of","Tomorrow","!" };
StrArr[1] = StrArr[1].ToUpper();
}
Find函数的使用
void ContaionArray()
{
TArray<FString> StrArr = { "12347756","33","44","Hello","World","of","Tomorrow","!" };
bool Hello = StrArr.Contains(TEXT("Hello"));
bool bGoodbye = StrArr.Contains(TEXT("Goodbye"));
int32 bFind5Character = 0;
bool bLen5 = StrArr.ContainsByPredicate([](const FString& Str)
{
++bFind5Character;
UE_Log(LogTemp,Warning,TEXT("bLen5,[%d]"),)
return Str.Len() == 5;
});
int32 bFind6Character = 0;
bool bLen6 = StrArr.ContainsByPredicate([](const FString& Str)
{
++bFind6Character;
UE_Log(LogTemp,Warning,TEXT("bLen6,[%d]"),)
return Str.Len() == 6;
});
}
void FindElementArray()
{
TArray<FString> StrArr = { "Hello","World","of","Tomorrow","Hello","!" };
int32 Index = -1;
bool bIndex = StrArr.Find(TEXT("Hello"),Index);
int32 LasetIndex = -1;
bool bIndexFromIndex = StrArr.FindLast(TEXT("Hello"),LastIndex);
int32 Index2 = StrArr.Find(TEXT("Hello"));
int32 IndexLast2 = StrArr.FindLast(TEXT("Hello"));
int32 IndexNone = StrArr.Find(TEXT("None"));
bool bIndexNone = INDEX_NONE == IndexNone;
}
各类查询函数
void FindElemntByKey()
{
TArray<FString> StrArr = { "Hello","World","of","Tomorrow","!" };
int32 Index = StrArr.IndexOfByKey(TEXT("Hello"));
int32 IndexP = StrArr.IndexofByPredicate([](const FString& Str)
{
return Str.Contains(TEXT("r"))
});
}
移除操作
void RemoveElement()
{
TArray<int32> ValArr;
int32 Temp[] = { 10,20,30,5,10,15,20,25,30 };
ValArr.Append(Temp,ValArr.Num());
ValArr.Remove(20);
ValArr.RemoveSingle(30);
ValArr.RemoveAt(2);
ValArr.Shrink();
ValArr.RemoveAt(99);
}
void RemoveElement()
{
TArray<int32> ValArr;
int32 Temp[] = { 10,20,30,5,10,15,20,25,30 };
ValArr.Append(Temp,9);
ValArr.RemoveAll([](int32 Val)
{
return Val % 3 == 0;
});
ValArr.Empty();
ValArr.Append(Temp,9);
ValArr.RemoveSwap(2);
ValArr.Empty();
ValArr.Append(Temp,9);
ValArr.RemoveSwap(2);
ValArr.RemoveAllSwap([](int32 Val)
{
return Val % 3 == 0;
});
}
运算符
void OperateArray()
{
TArray<int32> ValArr3;
ValArr3.Add(1);
ValArr3.Add(2);
ValArr3.Add(3);
auto VarArr4 = VarArr3;
TArray<int32> ValArr5 = ValArr3;
ValArr4[0] = 5;
TArray<int32> ValArr6;
ValArr6 += ValArr4;
TArray<int32> ValArr7 = { 1,1,1 };
ValArr7 = MoveTemp(ValArr6);
}
void OperateStrArray()
{
TArray<FString> FlavorArr1;
FlavorArr1.Emplace(TEXT("Chocolate"));
FlavorArr1.Emplace(TEXT("Vanilla"));
auto FlavorArr2 = FlavorArr1;
bool bComparison1 = FlavorArr1 == FlavorArr2;
for (auto& Str :FlavorArr2)
{
Str = Str.ToUpper();
}
bool bComparison2 = FlavorArr1 == FlavorArr2;
Exchange(FlavorArr2[0], FlavorArr2[1]);
bool bComparison3 = FlavorArr1 == FlavorArr2;
}
Slack元素
void SlackArray()
{
TArray<int32> SlackArray;
int32 SlackNum = SlackArray.GetSlack();
int32 Num = SlackArray.Num();
int32 Max = SlackArray.Max();
SlackArray().Add(1);
SlackNum = SlackArray.GetSlack();
int32 Num = SlackArray.Num();
int32 Max = SlackArray.Max();
SlackArray().Add(2);
SlackArray().Add(3);
SlackArray().Add(4);
SlackArray().Add(5);
}
void EmptyArray()
{
TArray<int32> SlackArray;
SlackArray.Add(10);
SlackArray.Empty(11);
SlackArray.Empty();
SlackArray.Empty(3);
SlackArray.Add(1);
SlackArray.Add(2);
SlackArray.Add(3);
}
void ResetArray()
{
TArray<int32> SlackArray;
SlackArray.Add(1);
SlackArray.Add(2);
SlackArray.Add(3);
SlackArray.Reset(0);
SlackArray.Reset(10);
SlackArray.Shrink();
}
原始内存
void OriginArray()
{
int32 SrcInts[] = { 2, 3, 5, 7 };
TArray<int32> UninitInts;
UninitInts.AddUninitialized(4);
FMemory::Memcpy(UninitInts.GetData(), SrcInts, 4*sizeof(int32));
TArray<FStructInfo> UninitStructs;
UninitStructs.AddUninitialized(4);
}
void OriginArray2()
{
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"]
TArray<FStructInfo> UninitStructs;
UninitStructs.Emplace(20);
UninitStructs.Emplace(50);
new ((void*)(UninitStructs.GetData() + 1)) FStructInfo(99);
new ((void*)(UninitStructs.GetData() + 2)) FStructInfo(88);
UninitStructs.Empty();
}
void ZeroArray()
{
TArray<FStructInfo> SArr;
SArr.AddZeroed();
}
void Test()
{
struct S
{
S(int32 InInt, void* InPtr, float InFlt)
:Int(InInt)
, Ptr(InPtr)
, Flt(InFlt)
{
}
int32 Int;
void* Ptr;
float Flt;
};
TArray<S> SArr;
SArr.AddZeroed();
// SArr == [{ Int:0, Ptr: nullptr, Flt:0.0f }]
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);
// SArr == [
// { Int:0, Ptr: nullptr, Flt:0.0f },
// { Int:5, Ptr:0x12345678, Flt:3.14f },
// { Int:2, Ptr:0x87654321, Flt:2.72f },
// { Int:0, Ptr: nullptr, Flt:0.0f },
// { Int:0, Ptr: nullptr, Flt:0.0f }
//
SArr.SetNumZeroed(5);
}