当前位置: 首页 > article >正文

学习虚幻C++开发日志——TSet

TSet

官方文档:虚幻引擎中的Set容器 | 虚幻引擎 5.5 文档 | Epic Developer Community (epicgames.com)

TSet 是通过对元素求值的可覆盖函数,使用数据值本身作为键,而不是将数据值与独立的键相关联。

默认情况下,TSet 不支持重复的键,但使用模板参数可激活此行为。

TSet 是一种快速容器类,用于在排序不重要的情况下存储唯一元素。TSet 可以非常快速地添加、查找和删除元素(恒定时间)。在大多数情况下,只需要一种参数——元素类型。但是,TSet 可配置各种模板参数来改变其行为,使其更全面。除了可指定从 DefaultKeyFuncs 的派生结构体来提供散列功能,还可允许集合中的多个键拥有相同的值。它和其它容器类一样,可设置自定义内存分配器来存储数据。

和 TArray 一样,TSet 是同质容器。TSet 也是值类型,支持常规复制、赋值和析构函数操作,以及其元素较强的所有权。TSet 被销毁时,其元素也将被销毁。键类型也必须是值类型。

TSet 使用散列,即如果给出了 KeyFuncs 模板参数,该参数会告知集合如何从某个元素确定键,如何比较两个键是否相等,如何对键进行散列,以及是否允许重复键。它们默认只返回对键的引用,使用 运算符== 对比相等性,使用非成员函数 GetTypeHash 进行散列。默认情况下,集合中不允许有重复的键。如果您的键类型支持这些函数,则可以将其用作集合键,无需提供自定义 KeyFuncs。要写入自定义 KeyFuncs,可扩展 DefaultKeyFuncs 结构体。

最后,TSet 可通过任选分配器控制内存分配行为。标准虚幻引擎4(UE4)分配器(如 FHeapAllocator 和 TInlineAllocator)不能用作 TSet 的分配器。实际上,TSet 使用集合分配器,该分配器可定义集合中使用的散列桶数量以及用于存储元素的标准UE4分配器。

与 TArray 不同的是,内存中 TSet 元素的相对排序既不可靠也不稳定,对这些元素进行迭代很可能会使它们返回的顺序和它们添加的顺序有所不同。这些元素也不太可能在内存中连续排列。集合中的后台数据结构是稀疏数组(在数组中有空位。从集合中移除元素时,稀疏数组中会出现空位)。将新的元素添加到阵列可填补这些空位。

但是,即便 TSet 不会打乱元素来填补空位,指向集元素的指针仍然可能失效,因为如果存储器被填满,又添加了新的元素,整个存储可能会重新分配。

1.创建和填充集合

创建一个MapActor类并继承于Actor,其与TArray创建方法一样就不一一详细介绍;

Actor类头文件增添代码:

public:
    UFUNCTION(BlueprintCallable)
    void InitSet();

源文件增添代码:

void ASetActor::InitSet()
{
    TSet<FString> FruitSet;

    //此处的元素按插入顺序排列,但不保证这些元素在内存中实际保留此排序。
    //如果是新集合,可能会保留插入排序,但插入和删除的次数越多,新元素不出现在末尾的可能性越大。
    FruitSet.Add(TEXT("Banana"));
	FruitSet.Add(TEXT("Grapefruit"));
	FruitSet.Add(TEXT("Pineapple"));
	// FruitSet == [ "Banana", "Grapefruit", "Pineapple" ]

    FruitSet.Add(TEXT("Pear"));
	FruitSet.Add(TEXT("Banana"));//此处与上面的键重复因此覆盖了,但此处会触发扩容
	// FruitSet == [ "Banana", "Grapefruit", "Pineapple", "Pear" ]
	// Note:Only one banana entry.

    //此处,参数直接传递给键类型的构造函数。这可以避免为该值创建临时 FString。
    //与 TArray 不同的是,只能使用单一参数构造函数将元素放到集合中。
    FruitSet.Emplace(TEXT("Orange"));//追加元素,用Emplace函数代替Add避免插入集合时创造临时文件
	// FruitSet == [ "Banana", "Grapefruit", "Pineapple", "Pear", "Orange" ]

    TSet<FString> FruitSet2;
	FruitSet2.Emplace(TEXT("Kiwi"));
	FruitSet2.Emplace(TEXT("Melon"));
	FruitSet2.Emplace(TEXT("Mango"));
	FruitSet2.Emplace(TEXT("Orange"));
	FruitSet.Append(FruitSet2);
	// FruitSet == [ "Banana", "Grapefruit", "Pineapple", "Pear", "Orange", "Kiwi", "Melon", "Mango" ]
}

注意元素以及Num的变化,从而去理解TSet键的原理 (使用数据值本身作为键)

2.编辑UPROPERTY TSet

如果用 UPROPERTY 宏和一个可编辑的关键词(EditAnywhereEditDefaultsOnly 或 EditInstanceOnly)标记 TSet,则可在虚幻编辑器中添加和编辑元素。

Actor类头文件增添代码:

public:
    UPROPERTY(BlueprintReadWrite,EditAnyWhere,Category = SetExample)
	TSet<FString> MyFruitSet;

在蓝图可编辑操作

此处为FString,但其也可以放结构体

3.迭代

Actor类头文件增添代码:

public:
    UFUNCTION(BlueprintCallable)
    void LoopSet();

源文件增添代码:

void ASetActor::LoopSet()
{
    TSet<FString> FruitSet;

    FruitSet.Add(TEXT("Banana"));
	FruitSet.Add(TEXT("Grapefruit"));
	FruitSet.Add(TEXT("Pineapple"));

    //依次输出元素
    for (auto& Elem :FruitSet)//此处的auto可换为FString,其本质时自动推导类型
	{
		FPlatformMisc::LocalPrint(*FString::Printf(TEXT(" \"%s\"\n"),*Elem));
	}

    //创建迭代器(如为CreateConstIterators 函数则为常量迭代器)
    for (auto It = FruitSet.CreateIterator(); It; ++It)
	{
        //注意后面*(*It),第一个*是把FString类型变成TCHAR,第二个是指针,因为迭代器It是地址
		FPlatformMisc::LocalPrint(*FString::Printf(TEXT("(%s)\n"),*(*It)));
	}

	for (auto It = FruitSet.CreateConstIterator(); It; ++It)
	{
		FPlatformMisc::LocalPrint(*FString::Printf(TEXT("(%s)\n"),*(*It)));
	}
}

4.查询

Actor类头文件增添代码:

public:
    UFUNCTION(BlueprintCallable)
    void QuerySet();

源文件增添代码:

void ASetActor::QuerySet()
{
    TSet<FString> FruitSet;

    bool bHave=false;
    FruitSet.Add(TEXT("Banana"),&bHave);//此处bHave为false
	FruitSet.Add(TEXT("Grapefruit"));
	FruitSet.Add(TEXT("Pineapple"));
    FruitSet.Add(TEXT("Banana"),&bHave);//此处使得bHave为true

	int32 Count = FruitSet.Num();
	// Count == 3

    //要确定集合是否包含特定元素,可按如下所示调用 Contains 函数
    bool bHasBanana = FruitSet.Contains(TEXT("Banana"));
	bool bHasLemon = FruitSet.Contains(TEXT("Lemon"));
	// bHasBanana == true
	// bHasLemon == false

    //使用 FSetElementId 结构体可查找集合中某个键的索引。
    FSetElementId SetElementId=Fruit.Add(TEXT("Water"));//FSetElementId为标识符,此处SetElementId={Index=3}
    FruitSet[SetElementId]+=TEXT("Modify");//此处将Index为3的元素进行修改为"WaterModify"

    //使用 Find 函数查找一次即可完成这些行为。
    //如果集合中包含该键,Find 将返回指向元素数值的指针。如果映射不包含该键,则返回null。对常量集合调用Find,返回的指针也将为常量。
    FString* PtrBanana = FruitSet.Find(TEXT("Banana"));
	FString* PtrLemon = FruitSet.Find(TEXT("Lemon"));
	// *PtrBanana == "Banana"
	//  PtrLemon == nullptr

    //Array 函数会返回一个 TArray,其中填充(覆盖,即使有元素在内)了 TSet 中每个元素的一份副本。因此元素的生成数量将始终等于集合中的元素数量
    TArray<FString> FruitArray = FruitSet.Array();//用Array函数转换为TArray数组
}

 5.移除

Actor类头文件增添代码:

public:
    UFUNCTION(BlueprintCallable)
    void RemoveSet();

源文件增添代码:

void ASetActor::RemoveSet()
{
    TSet<FString> FruitSet;
    FruitSet.Reserve(4);
    FruitSet.Add(TEXT("Banana"));
	FruitSet.Add(TEXT("Grapefruit"));
	FruitSet.Add(TEXT("Pineapple"));
    FruitSet.Add(TEXT("GG"));
    FruitSet.Add(TEXT("HH"));
    FruitSet.Add(TEXT("JJ"));

    //Remove函数有两种使用参数方法
    FruitSet.Remove(FSetElementId::FromInterger(0));//通过索引移除第一个元素
    //Remove函数会返回已删除元素的数量。
    int32 GGNum=FruitSet.remove(TEXT("GG"));//GGNum=1
    int32 MMNum=FruitSet.remove(TEXT("MM"));//MMNum=0

    TSet<FString> FruitSet1=FruitSet;
    FruitSet1.Reset();//将集合中的所有元素移除,但内存空间还在
    TSet<FString> FruitSet2=FruitSet;
    FruitSet2.Empty(0);//此操作是将元素及其内存空间都删除
}

6.排序&运算符&Slack

6.1排序

TSet 可以排序。排序后,迭代集合会以排序的顺序显示元素,但下次修改集合时,排序可能会发生变化。由于排序不稳定,可能按任何顺序显示集合中支持重复键的等效元素。

Actor类头文件增添代码:

public:
    UFUNCTION(BlueprintCallable)
    void SortSet();

源文件增添代码:

void ASetActor::SortSet()
{
    TSet<FString>FruitSet={ "Orange","Pear", "Melon", "Grapefruit", "Mango", "Kiwi"};
    FruitSet.Sort([](const FString& A, const FString& B) {
		return A > B; // sort by reverse-alphabetical order
	});
	// FruitSet == [ "Pear", "Orange", "Melon", "Mango", "Kiwi", "Grapefruit" ] (order is temporarily guaranteed)
 
    //Sort 函数使用指定排序顺序的二进制谓词
	FruitSet.Sort([](const FString& A, const FString& B) {
		return A.Len() < B.Len(); // sort strings by length, shortest to longest
	});
	// FruitSet == [ "Pear", "Kiwi", "Melon", "Mango", "Orange", "Grapefruit" ] (order is temporarily guaranteed)
}

6.2运算符

其和 TArray 一样,TSet 是常规值类型,可通过标准复制构造函数赋值运算符进行复制。因为集合严格拥有其元素,复制集合的操作是深层的,所以新集合将拥有其自身的元素副本。

Actor类头文件增添代码:

public:
    UFUNCTION(BlueprintCallable)
    void OpeatorSet();

源文件增添代码:

void ASetActor::SortSet()
{
    TSet<FString>FruitSet={ "Orange","Pear", "Melon", "Grapefruit", "Mango", "Kiwi"};
    TSet<FString> NewSet = FruitSet;

    //在新的进行增删改查
	NewSet.Add(TEXT("Apple"));
	NewSet.Remove(TEXT("Pear"));
}
//在移除目录处也有提及

6.3Slack 

其用法与TMap容器大致相似

Actor类头文件增添代码:

public:
    UFUNCTION(BlueprintCallable)
    void SlackSet();

源文件增添代码:

void ASetActor::SortSet()
{
    TSet<FString>FruitSet={ "Orange","Pear", "Melon", "Grapefruit", "Mango", "Kiwi"};
    
    //Reset函数可在不取消任何内存的情况下移除集合中的所有元素,从而产生slack
    FruitSet.Reset();

	FruitSet.Reserve(10);//在原基础上追加预分配10个内存
	for (int32 i = 0; i < 10; ++i)
	{
		FruitSet.Add(FString::Printf(TEXT("Fruit%d"), i));
	}
// FruitSet == [ "Fruit9", "Fruit8", "Fruit7" ..."Fruit2", "Fruit1", "Fruit0" ]

// Remove every other element from the set.
	for (int32 i = 0; i < 10; i += 2)
	{
		FruitSet.Remove(FSetElementId::FromInteger(i));
	}
// FruitSet == ["Fruit8", <invalid>, "Fruit6", <invalid>, "Fruit4", <invalid>, "Fruit2", <invalid>, "Fruit0", <invalid> ]

    //Shrink裁剪元素
	FruitSet.Shrink();
    //注意此处数组Max值为10 
// FruitSet == ["Fruit8", <invalid>, "Fruit6", <invalid>, "Fruit4", <invalid>, "Fruit2", <invalid>, "Fruit0" ]

    //CompactStable函数压缩元素,Compact函数则可能改变排序
    FruitSet.CompactStable();
	// FruitSet == ["Fruit8", "Fruit6", "Fruit4", "Fruit2", "Fruit0", <invalid>, <invalid>, <invalid>, <invalid> ]
	
    FruitSet.Shrink();
    // FruitSet == ["Fruit8", "Fruit6", "Fruit4", "Fruit2", "Fruit0" ]
}

 


http://www.kler.cn/news/363528.html

相关文章:

  • 【Flutter】基础组件:文本及样式
  • 实验03分支---7-10 计算出租车费
  • 程序员:代码世界的探险家与日常“救火队员”
  • 零售行业的数字化营销转型之路
  • 【python + Redis】hash值查增删
  • CODESYS随机动态图案验证码制作详细案例(三)
  • Oracle 更换监听端口
  • 大模型涌现判定
  • 每天五分钟深度学习pytorch:L1和L2范数、L1和L2归一化
  • Spring面试题
  • Deformable Detr
  • 几张图就让你掌握InnoDB 存储引擎底层逻辑架构
  • linux_c IPC消息队列练习
  • OpenHarmony 目前所有体系详细介绍
  • Git的多人协作模式与企业级开发模型
  • 【NodeJS】NodeJS+mongoDB在线版开发简单RestfulAPI (三):Cors的设置及.env文件的设置
  • 2024年03月中国电子学会青少年软件编程(图形化)等级考试试卷(四级)答案 + 解析
  • java字段判空方法Assert.hasText()详细讲解
  • 智慧城市垃圾分类可视化
  • 提示词高级阶段学习day3.1什么是结构化 Prompt ?
  • 算法魅力-双指针之滑动窗口的叛逆
  • 吴恩达深度学习笔记:卷积神经网络(Foundations of Convolutional Neural Networks)3.9-3.10
  • 【vue + mockjs】Mockjs——数据接口模拟
  • git clone卡在Receiving objects
  • matlab生成mipi crc值
  • MySQL 中的连表是怎样实现的?为什么大厂不使用连表查询?