UE_C++ —— Container TArray

目录

一,TArray

二,Creating and Filling an Array

三,Iteration

四,Sorting

五,Queries

六,Removal

七,Operators

八,Heap

九,Slack

十,Raw Memory

十一,Miscellaneous


        UE中最简单的容器类是 TArray,负责同类型其他对象(称为"元素")序列的所有权和组织;由于 TArray 是一个序列,其元素的排序定义明确,其函数用于确定性地操纵此类对象及其顺序;

一,TArray

        TArray 是UE中最常用的容器类,其速度快、内存消耗小、安全性高;TArray 类型由两大属性定义:元素类型和可选分配器;

        元素类型是存储在数组中的对象类型,TArray 被称为同质容器;换言之,其所有元素均完全为相同类型;单个 TArray 中不能存储不同类型的元素;

        分配器常被省略,默认为最常用的分配器;其定义对象在内存中的排列方式,以及数组如何进行扩展,以容纳更多的元素;若默认行为不符合要求,可选取多种不同的分配器,或自行编写;

        Tarray 为值类型,意味其与其他内置类型(如 int32 或 浮点)的处理方式相同;它不是为扩展而设计的,不建议使用new和delete创建或销毁TArray实例;元素也为数值类型,为容器所拥有;TArray 被销毁时其中的元素也将被销毁;若在另一TArray中创建TArray变量,其元素将复制到新变量中,且不会共享状态;

二,Creating and Filling an Array

        创建用于存储整数序列的空数组,其元素类型是可根据普通C++值规则进行复制和销毁的数值类型,如 int32FStringTSharedPtr 等;由于无指定分配器,因此 TArray 将采用基于堆的默认分配器;此时尚未分配内存;

//创建用于存储整数序列的空数组
TArray<int32> IntArray;

有多种填充 Tarray 的方法:

//填充 Tarray 的方法:使用 Init 函数,将大量元素副本填入数组;
IntArray.Init(10, 5);
// IntArray == [10,10,10,10,10]
//填充 Tarray 的方法:使用 Add 和 Emplace 函数用于在数组末尾添加对象;
TArray<FString> StrArr;
StrArr.Add(TEXT("Hello"));
StrArr.Emplace(TEXT("World"));
// StrArr == ["Hello","World"]

        新元素添加到数组时,分配器将根据需要分配内存;当数组大小超出时,默认分配器将添加足够的内存,以存储多个新元素;Add 和 Emplace 函数的多数效果相同,细微区别在于:

  • Add(或 Push)将元素类型的实例复制(或移动)到数组中;
  • Emplace 使用给定参数构建元素类型的新实例;

        因此在 TArray<FString> 中,Add 将用字符串文字创建临时 FString,然后将该临时 FString 的内容移至容器内的新 FString 中,而 Emplace 将用字符串文字直接新建 FString;最终结果相同,但 Emplace 可避免创建临时文件;对于 FString 等非浅显数值类型而言,临时文件通常不理想;

        总之,Emplace 优于 Add,因此其可避免在调用点创建无需临时变量,并将此类变量复制或移动到容器中;根据经验,可将 Add 用于浅显类型,将 Emplace 用于其他类型;Emplace 的效率始终高于 Add,但 Add 的可读性可能更好;

//Append 可一次性添加多个元素,从其他TArray或指向常规C数组的指针及该数组的大小
FString Arr[] = { TEXT("of"), TEXT("Tomorrow") };
StrArr.Append(Arr, ARRAY_COUNT(Arr));
// StrArr == ["Hello","World","of","Tomorrow"]
//AddUnique 仅添加新元素(不存在相等的元素),使用 operator== 检查;
StrArr.AddUnique(TEXT("!"));
// StrArr == ["Hello","World","of","Tomorrow","!"]

StrArr.AddUnique(TEXT("!"));
// StrArr is unchanged as "!" is already an element
//与 Add、Emplace 和 Append 相同,Insert 将在给定索引处添加单个元素或元素数组的副本;
StrArr.Insert(TEXT("Brave"), 1);
// StrArr == ["Hello","Brave","World","of","Tomorrow","!"]
//SetNum 函数可直接设置数组元素的数量
//如新数量大于当前数量,则使用元素类型的默认构造函数新建元素;
StrArr.SetNum(8);
// StrArr == ["Hello","Brave","World","of","Tomorrow","!","",""]

//新数量小于当前数量,SetNum 将移除元素;
StrArr.SetNum(6);
// StrArr == ["Hello","Brave","World","of","Tomorrow","!"]

三,Iteration

        有多种方法可迭代数组的元素,建议使用C++的范围(ranged-for)功能;

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(" ");
}

        还可通过数组迭代器类型控制迭代,函数 CreateIterator 和 CreateConstIterator 可分别用于元素的读写和只读访问;

for (auto It = StrArr.CreateConstIterator(); It; ++It)
{
    JoinedStr += *It;
    JoinedStr += TEXT(" ");
}

四,Sorting

        调用 Sort 函数即可对数组进行排序;数值按元素类型的 operator< 排序,在FString中,此为忽略大小写的比较;

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

        字符串按长度排序,长度相同的字符串"Hello"、"Brave"和"World"的相对排序发生了变化;是因为 Sort 不稳定,等值元素的相对排序无法保证;Sort 作为quicksort实现;

        HeapSort 函数,是否使用binary predicate,均可执行堆排序;使用HeapSort函数与否,取决于特定数据与Sort函数相比时的排序效率;与 Sort 一样,HeapSort 也不稳定;

StrArr.HeapSort([](const FString& A, const FString& B) {
    return A.Len() < B.Len();
    });
// StrArr == ["!","of","Hello","Brave","World","Tomorrow"]

        StableSort 用于在排序后保证等值元素的相对顺序,StableSort 作为归并排序实现;

StrArr.StableSort([](const FString& A, const FString& B) {
    return A.Len() < B.Len();
    });
// StrArr == ["!","of","Brave","Hello","World","Tomorrow"]

五,Queries

        使用 Num 函数可查询数组保存的元素数量;

int32 Count = StrArr.Num();
// Count == 6

        如需直接访问数组内存,可使用 GetData 函数将指针返回到数组中的元素;仅在数组存在且未执行更改数组的操作时,此指针方有效;仅 StrPtr 的首个 Num 索引才可被解引用;如容器为常量,则返回的指针也为常量;

FString* StrPtr = StrArr.GetData();
// StrPtr[0] == "!"
// StrPtr[1] == "of"
// ...
// StrPtr[5] == "Tomorrow"
// StrPtr[6] - undefined behavior​

        对容器元素大小进行询问;

uint32 ElementSize = StrArr.GetTypeSize();
// ElementSize == sizeof(FString)

        使用 operator[]获取元素,从0开始索引;传递小于0或大于等于Num()的无效索引将导致运行时错误,使用 IsValidIndex 函数询问容器,可确定特定索引是否有效;operator[]将返回引用,因此还可用于改变数组中的元素;

FString Elem1 = StrArr[1];
// Elem1 == "of"
bool bValidM1 = StrArr.IsValidIndex(-1);
bool bValid0 = StrArr.IsValidIndex(0);
bool bValid5 = StrArr.IsValidIndex(5);
bool bValid6 = StrArr.IsValidIndex(6);
// bValidM1 == false
// bValid0  == true
// bValid5  == true
// bValid6  == false
StrArr[3] = StrArr[3].ToUpper();
// StrArr == ["!","of","Brave","HELLO","World","Tomorrow"]

        与GetData函数相同:如数组为常量,operator[] 将返回常量引用;还可使用 Last 函数从数组末端反向索引,索引默认为零;Top 函数是 Last 的同义词,唯一区别是其不接受索引;

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

        使用 Find 函数族可查找元素,可使用Find确定元素是否存在并返回其索引,找到的为首个元素索引,如存在重复元素而希望找到最末元素的索引,则使用 FindLast 函数;

int32 Index;
if (StrArr.Find(TEXT("Hello"), Index)) //Index为首个元素的索引
{
    // Index == 3
}
int32 IndexLast;
if (StrArr.FindLast(TEXT("Hello"), IndexLast))
{
    // IndexLast == 3, because there aren't any duplicates
}

        Find 和 FindLast 也可直接返回元素索引;如不将索引作为显式参数传递,这两个函数便会执行此操作;此将比上述函数更简洁,使用的函数则取决于特定需求或风格;如未找到元素,将返回特殊 INDEX_NONE 值;

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 的工作方式类似,但元素可与任意对象比较;开始搜索前,使用 Find 函数会将参数实际转换为元素类型(此本例中为 FString);使用 IndexOfByKey,可直接对"键"比较,因此即使键类型无法直接转换为元素类型,也可进行搜索;

        IndexOfByKey 适用于存在 operator==(ElementType, KeyType) 的键类型;IndexOfByKey 将返回找到的首个元素的索引;如未找到元素,则返回 INDEX_NONE

int32 Index = StrArr.IndexOfByKey(TEXT("Hello"));
// Index == 3

        IndexOfByPredicate 函数用于查找与特定谓词匹配的首个元素的索引;如未找到,同样返回特殊 INDEX_NONE 值;

int32 Index = StrArr.IndexOfByPredicate([](const FString& Str) {
    return Str.Contains(TEXT("r"));
    });
// Index == 2

        可将指针返回指向找到的元素,而不返回索引;FindByKey 与 IndexOfByKey 相似,将元素和任意对象进行对比,但返回指向所找到元素的指针;如未找到元素,则返回nullptr

auto* OfPtr = StrArr.FindByKey(TEXT("of")));
auto* ThePtr = StrArr.FindByKey(TEXT("the")));
// OfPtr  == &StrArr[1]
// ThePtr == nullptr

        FindByPredicate 的使用方式和 IndexOfByPredicate 相似,不同点是返回指针而非索引;

auto* Len5Ptr = StrArr.FindByPredicate([](const FString& Str) {
    return Str.Len() == 5;
    });
auto* Len6Ptr = StrArr.FindByPredicate([](const FString& Str) {
    return Str.Len() == 6;
    });
// Len5Ptr == &StrArr[2]
// Len6Ptr == nullptr

        使用 FilterByPredicate 函数可获取与特定谓词匹配的元素数组;

auto Filter = StrArray.FilterByPredicate([](const FString& Str) {
    return !Str.IsEmpty() && Str[0] < TEXT('M');
    });

六,Removal

        Remove 函数族用于移除数组中的元素,将根据元素类型的 operator== 函数移除所有与提供元素等值的元素;

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 函数用于按从零开始的索引移除元素;可使用 IsValidIndex 确定数组中的元素是否使用计划提供的索引,将无效索引传递给此函数会导致运行时错误;

ValArr.RemoveAt(2); // Removes the element at index 2	
// ValArr == [10,5,15,25,30] 	
ValArr.RemoveAt(99); // This will cause a runtime error as there is no element at index 99

        RemoveAll 也可用于函数移除与谓词匹配的元素;

ValArr.RemoveAll([](int32 Val) {
    return Val % 3 == 0;
    });
// ValArr == [10,5,25]

注,在所有这些情况中,由于数组中不能出现空位,因此移除元素时其后的元素将被下移到更低索引中;

        移动过程存在开销,如不需剩余元素排序,可使用 RemoveSwapRemoveAtSwap 和 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 == []

七,Operators

        数组是常规值类型,可使用标准复制构造函数或赋值运算符进行复制;由于数组严格拥有其元素,复制数组的操作是深拷贝,因此新数组将拥有其自身的元素副本;

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 函数的替代,可使用 operator+= 对数组进行串联;

ValArr4 += ValArr3;	
// ValArr4 == [5,2,3,1,2,3]

        TArray 还支持移动语义,使用 MoveTemp 函数可调用这些语义;移动后,源数组必定为空;

ValArr3 = MoveTemp(ValArr4);
// ValArr3 == [5,2,3,1,2,3]
// ValArr4 == []

        使用 operator== 和 operator!= 对数组进行比较;元素的排序很重要:只有元素的顺序和数量相同时,两个数组才被视为相同;元素通过其自身的 operator== 进行比较;

TArray<FString> FlavorArr1;
FlavorArr1.Emplace(TEXT("Chocolate"));
FlavorArr1.Emplace(TEXT("Vanilla"));
// FlavorArr1 == ["Chocolate","Vanilla"]

auto FlavorArr2 = Str1Array;
// 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

八,Heap

        TArray 拥有支持二叉堆数据结构的函数;堆是一种二叉树,使用数组实现时,树的根节点位于元素0,索引N处节点的左右子节点的指数分别为2N+1和2N+2;子节点彼此间不存在特定排序;

        调用 Heapify 函数可将现有数组转换为堆;此会重载为是否接受谓词,无谓词的版本将使用元素类型的 operator< 确定排序;

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]

注,仅在已是有效堆时(如在 Heapify 调用、其他堆操作或手动将数组操作到堆中之后),才应调用 HeapPushHeapPopHeapPopDiscard 和 HeapRemoveAt

        可使用 HeapTop 检查堆的顶部节点,无需变更数组;

int32 Top = HeapArr.HeapTop();
// Top == 2

九,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不是常量;

        虽然无需管理Slack,但可管理Slack对数组进行优化,以满足需求;如需要向数组添加大约100个新元素,则可在添加前确保拥有可至少存储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;此将把内存分配调整为保存当前元素所需的最小内存;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

十,Raw Memory

        本质上,TArray 只是分配内存的包装器;直接修改分配的字节和自行创建元素即可将其用作包装器,此操作十分实用;Tarray 将尽量利用其拥有的信息进行执行,但有时需降低等级;

注,利用以下函数可在较低级别快速访问 TArray 及其数据,但若利用不当,可能会导致容器无效和未知行为;在调用此类函数后(但在调用其他常规函数前),可决定是否将容器返回有效状态;

        AddUninitialized 和 InsertUninitialized 函数可将未初始化的空间添加到数组;两者工作方式分别与 Add 和 Insert 函数相同,只是不调用元素类型的构造函数;若要避免调用构造函数,建议使用此类函数;

int32 SrcInts[] = { 2, 3, 5, 7 };
TArray<int32> UninitInts;
UninitInts.AddUninitialized(4);
FMemory::Memcpy(UninitInts.GetData(), SrcInts, 4 * sizeof(int32));
// UninitInts == [2,3,5,7]
//也可使用此功能保留计划自行构建对象所需内存
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"]

        AddZeroed 和 InsertZeroed 的工作方式相似,不同点是会将添加/插入的空间字节清零;

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

        SetNumUninitialized 和 SetNumZeroed 函数的工作方式与 SetNum 类似,不同之处在于新数量大于当前数量时,将保留新元素的空间为未初始化或按位归零。与 AddUninitialized 和 InsertUninitialized 函数相同,必要时需将新元素正确构建到新空间中;

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  }
	// ]

注,应谨慎使用"Uninitialized"和"Zeroed"函数族。如函数类型包含要构建的成员或未处于有效按位清零状态的成员,可导致数组元素无效和未知行为。此类函数适用于固定的数组类型,例如FMatrix和FVector;

十一,Miscellaneous

        BulkSerialize 函数是序列化函数,可用作替代 operator<<,将数组作为原始字节块进行序列化,而非执行逐元素序列化;如使用内置类型或纯数据结构体等浅显元素,可改善性能;

        CountBytes 和 GetAllocatedSize 函数用于估算数组当前内存占用量;CountBytes 接受 FArchive,可直接调用 GetAllocatedSize;此类函数常用于统计报告;

        Swap 和 SwapMemory 函数均接受两个索引并交换此类索引上的元素值;这两个函数相同,不同点是 Swap 会对索引执行额外的错误检查,并断言索引是否超出范围;

在Unreal Engine (UE5) 中,要加载动态网页图表(通常指WebGL或HTML5图表),你可以利用其插件系统或直接通过网络请求将数据发送到浏览器,并利用浏览器的JavaScript API来显示图表。以下是一个基本步骤: 1. **使用插件**: - UE5支持插件如`Unreal Web Browser`,可以方便地在游戏内嵌入Web浏览器组件。首先安装并配置这个插件,在蓝图中创建WebBrowser实例,然后设置URL为包含图表的网页地址。 ```blueprint // 创建WebBrowser组件 WebBrowserComponent = CreateDefaultSubobject<WebBrowserComponent>(TEXT("MyWebBrowser")); // 设置网页URL SetProperty(WebBrowserComponent, TEXT("URL"), TEXT("http://your-chart-url.com")); ``` 2. **网络请求与JavaScript通信**: - 如果你想从服务器获取数据并在客户端动态生成图表,可以使用`FHttpActor`或自定义C++网络请求。获取数据后,可以将其作为JSON或者其他格式传递给浏览器,然后在JS中解析并渲染图表。 ```cpp FHttpRequest Request; Request.SetURL("http://your-api-url.com/get_chart_data"); FHttpResponseHandler ResponseHandler([this](TSharedRef<FHttpRequestInfo> HttpRequestInfo, EIfStatus::Type IfStatus, FString ContentType, const TArray<uint8>& Data) { // 处理返回的数据,例如解析JSON TSharedPtr<FJsonObject> JsonData = TJsonParser::Parse(Data)->GetRootObject(); // 将数据传递给WebBrowser组件 if (JsonData.IsValid()) { WebBrowserComponent->SetJavascriptFunction(TEXT("renderChart"), JsonData); } }); SendRequest(Request); ``` 3. **在JavaScript中处理**: - 在浏览器端,你需要编写JavaScript代码来接收和解析服务器传递的数据,并使用合适的图表库(如ECharts、D3.js等)绘制图表。 ```javascript function renderChart(data) { var chartData = JSON.parse(data); // 使用图表库生成图表 Highcharts.chart('container', { series: [ dataFromChartData(chartData) ] }); } // 在适当的地方调用renderChart函数 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值