➤ TArray容器
TArray:是UE4中最简单也是最常用的容器类,具有速度快,内存消耗小,安全性高的特点。TArray类模板可传入两个参数,第一个参数是容器的元素类型,第二个参数是可选的内存分配器,最常用的就是默认的分配器,分配器定义对象在内存中的排列方式,以及容器动态扩容的方式。TArray容器可以当成是一个数组,可随机访问。
需要注意的是:
① 在给数组添加元素,特别是一次性添加多个元素时,可能会触发容器的扩容,使得整个容器重新分配内存空间,此时如果在此之前通过 GetData() 获取了原始数组首地址指针的话,会导致原来保存的首地址指针变成野指针,等效于对指针进行了 delete;
② 在数组尾部追加元素常用 Add() 和 Emplace() 接口,它们类似于C++中STL的 push_back() 和 emplace_back(),Add() 会先构造一个元素然后将其复制或移动数组尾部,而 Emplace() 会直接在数组尾部构造元素,Emplace() 的效率优于 Add(),大部分情况下优先使用 Emplace(),但对于基础数据类型 使用 Add() 会有更好的可读性;
③ 移除单个元素时,如果移除的是数组尾部元素,则元素不会立即析构,若移除的是数组中间的元素,则会立即析构,且数组还需要移动一部分元素,保证数组元素内存的连续性(这期间也会有析构和构造的过程);
④ 移除单个元素接口 Remove() 会在内部对传入的左值引用参数使用接口 CheckAddress() 进行地址检查,如果地址被查出属于输入数组的地址段,则会触发断言,所以不能有类似 arr.Remove( arr[0] ) 这种用法;
⑤ 移除所有元素接口 RemoveAll(),和一般理解的不同,需要传入一个移除规则,一般用一个 lambda 函数,返回true时表示传入的元素需要被移除;
⑥ 按照官方文档的说法,因为TArray设计时未考虑拓展问题,所以不要使用 new/delete 创建或销毁 TArray 实例,即使在语法上是允许的;
⑦ Append 追加多个元素是浅拷贝;
使用示例:
(1)声明TArray容器:
// 声明一个容器,可以不初始化
TArray<int> arr;
// 使用初始化列表定义
TArray<int> arr1{ 1,2,3,4,5 };
// 使用Init接口初始化
TArray<int> arr2;
arr2.Init(666, 5); // 初始化为5个默认值为666的元素
// 通过初始化列表变量来赋值
TArray<int> arr3;
arr3 = { 6,7,8,9,10 };
(2)往容器添加元素:
// 适用于基础类型的添加元素
arr.Add(111);
// 效率更高的添加元素
arr.Emplace(222);
// 通过右值追加多个元素
arr.Append({ 333,444,555 });
// 将另一个数组元素追加到当前数组,浅拷贝
arr.Append(arr2.GetData(), arr1.Num());
// 也可直接传入另一个数组元素变量,浅拷贝
arr.Append(arr3);
// 往数组中添加唯一元素,如果元素已经存在,则不会添加
arr.AddUnique(888);
// 之前已添加,下条语句不会增加数组元素
arr.AddUnique(888);
// 在指定下标处插入新元素
arr.Insert(123, 0); // 在下标0处插入元素123
// 另一类在指定下标出插入新元素接口
arr.EmplaceAt(0, 456); // 在下标0处插入元素456
(3)容器排序:
// 默认排序,按照数据类型的 < 操作符进行排序
arr.Sort();
// 指定排序规则,一般为一个 lambda 表达式
arr.Sort([](const int& a, const int& b) {
return (a > b); // 自定义降序排序
});
(4)容器元素存在性判断:
// 获取数组的原始数组首地址,这里相当于获取到了 int[] 类型首地址
int* pArr = arr.GetData();
// 判断给定下标是否对于容器是否有效,本质即使拿到长度判断下标区间
bool bIdx = arr.IsValidIndex(100);
// 获取容器当前已存在的元素数量
unsigned int elementNum = arr.Num();
// 获取元素所代表的类型占用内存大小(字节)
unsigned int elementSize = arr.GetTypeSize();
// 获取当前容器占用内存总大小,这里并非等于元素数量乘上元素类型内存大小,
// 一般要大于这个值,因为实际获取的是容器在扩容时已经申请的内存大小
unsigned int totalSize = arr.GetAllocatedSize();
// 查找指定元素,没找到返回 -1
int index = arr.Find(2333);
// 查找指定元素在容器中是否存在
bool bContains = arr.Contains(456);
// 依据自定义规则查找元素在容器的存在性,一般是一个 lambda 表达式
bool bContainCondition = arr.ContainsByPredicate([](const int& num) {
return (num > 100); // 是否存在大于100的元素
});
(5)容器迭代:
// 下标边界迭代
for (int idx = 0; idx < arr.Num(); ++idx)
UE_LOG(LogTemp, Log, TEXT("Pring 1: %d"), arr[idx]);
// C++11新标准元素迭代
for (auto& num : arr)
UE_LOG(LogTemp, Log, TEXT("Print 2: %d"), num);
// UE4迭代器
for (auto itr = arr.CreateIterator(); itr; ++itr)
UE_LOG(LogTemp, Log, TEXT("Print 3: %d"), *itr);
(6)容器的运算符:
// 赋值操作,浅拷贝
TArray<int> arr4 = arr2;
arr4[0] = 888; // 不影响 arr2
// 串联数组,也是浅拷贝,是对 Append 函数的替代,
// 但本质还是通过 Append 配合 MoveTemp 语义操作
TArray<int> arr5{1,2,3};
arr5 += arr4;
arr5[3] = 999; // 不影响 arr4
// 同类型数组判等操作,首先判断数组长度是否相等,
// 然后对每个元素,通过其自身重载的 == 和 != 进行比较
TArray<int> arr6{5,6,7};
TArray<int> arr7{8,9,0};
bool bEqual = (arr6 == arr7); // false
bool bUnequal = (arr6 != arr7); // true
// 移动语义,类似标准C++中的 std::move,转移内存所有权
arr6 = MoveTemp(arr7); // arr6拥有arr7的内存数据,arr7为空
(7)容器元素的移除:
// 如果是移除尾部元素,不会立即析构元素
arr.RemoveAt(arr.Num() - 1);
// 移除非尾部元素,会立即析构,并且移动其后元素保证内存连续性
arr.RemoveAt(0);
// 移除指定元素,若移除中间元素会立即析构,移除尾部元素则不会
// 内部有地址检查,如果传入的是元素本身的引用,会触发断言
arr.Remove(333);
arr.Remove(arr[5]); // 运行时错误,触发断言
// 若要使用[]操作符移除指定元素,可使用 RemoveSingle
arr.RemoveSingle(arr[5]);
// 重设容器容量,如果重设容量小于当前容量,
// 则将多余元素从尾部起移除,移除不会立即析构,离开作用域后析构
arr.SetNum(7);
// 可自定义规则移除元素,一般为一个 lambda 表达式
arr.RemoveAll([](const int& num) {
// 移除元素等于456 的元素,返回true时移除
// 移除中间元素时会被立即析构,移除尾部元素则不会
return (num == 456);
});
// RemoveAll容易让人理解为移除全部元素,可以硬编码返回true
arr.RemoveAll([](const int& num) {
// 移除所有元素,不会立即析构,离开作用域后析构
return true;
});
补充:C++的STL中有vector等众多容器,为何UE4需要添加一个TArray?使用TArray可以提供更好的跨平台性,可以和其它UE4中如智能指针等引擎自定义类型无缝协作,同时,TArray针对游戏开发的特性提供了更多的实现,比如TArray可以作为一个堆来使用。
转载请注明:XAMPP中文组官网 » UE4随笔:TArray容器