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

TMap

官方文档:Map Containers in Unreal Engine | 虚幻引擎 5.5 文档 | Epic Developer Community (epicgames.com)

TMap 与 TSet 类似,它们的结构均基于对键进行散列运算。但与 TSet 不同的是,此容器将数据存储为键值对TPair<KeyType, ValueType>),只将键用于存储获取

键值对(Key-Value Pair)是一种常见的数据结构,它将一个值(Value)与一个唯一的键(Key)相关联,通过键可以快速地访问和获取对应的值。

映射有两种类型:TMap 和 TMultiMap

(1)TMap 中的键是唯一的,在 TMap 中添加新的键值时,若所用的键与原有的对相同,新对将替换原有的对。

(2)TMultiMap 可存储多个相同的键,在 TMultiMap 中,容器可以同时存储新对和原有的对。

在 TMap 中,键值对被视为映射的元素类型,相当于每一对都是个体对象。

注意:映射的支持数据结构是稀疏数组,这种数组可有效支持元素之间的空位。当元素从映射中被移除时,稀疏数组中就会出现空位。将新的元素添加到数组可填补这些空位。但是,即便 TMap 不会打乱元素来填补空位,指向映射元素的指针仍然可能失效,因为如果存储器被填满,又添加了新的元素,整个存储可能会重新分配。

TMap 是同质容器,就是说它所有元素的类型都应完全相同。

TMap 是值类型,支持通常的复制、赋值和析构函数运算,以及它的元素的强所有权。在映射被销毁时,它的元素都会被销毁。

TMap 是散列容器,这意味着键类型必须支持 GetTypeHash 函数,并提供 运算符== 来比较各个键是否等值。

TMap 可使用任选分配器来控制内存分配行为。但不同于 TArray,这些是集合分配器(集合分配器(TSetAllocator类)定义映射应使用的散列桶数量,以及应使用哪个标准UE4分配器来存储散列和元素。)。

TMap 模板最后一个参数——KeyFuncs,该参数告知映射如何从元素类型获取键,如何比较两个键是否相等,以及如何对键进行散列计算

注意:与 TArray 不同的是,内存中 TMap 元素的相对排序既不可靠也不稳定,对这些元素进行迭代很可能会使它们返回的顺序和它们添加的顺序有所不同。这些元素也不太可能在内存中连续排列。

1.创建和填充映射

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

Actor类头文件增添代码:

public:
    UFUNCTION(BlueprintCallable)
    void InitMap();

    UPROPERTY(BlueprintReadWrite,EditAnywhere,Category = "Name|Fruit")//其在蓝图内可读可写
	TMap<int32, FString> MyFruitMap;

用 UPROPERTY 宏和一个可编辑的关键词(EditAnywhereEditDefaultsOnly 或 EditInstanceOnly)标记 TMap,即可在编辑器中添加和编辑元素

源文件函数代码:

void AMapActor::InitMap()
{
    TMap<int32, FString> FruitMap;

    //填充映射的标准方法是调用带一个键和值的 Add 函数
    FruitMap.Add(5, TEXT("Banana"));
	FruitMap.Add(2, TEXT("Grapefruit"));
	FruitMap.Add(7, TEXT("Pineapple"));
	// FruitMap == [
	// 	{ Key:5, Value:"Banana"     },
	// 	{ Key:2, Value:"Grapefruit" },
	// 	{ Key:7, Value:"Pineapple"  }
	// ]

    //每个键都是唯一的,如果尝试添加重复键,将会将之前键值为2的“Grapefruit”被“Pear”替代
    FruitMap.Add(2, TEXT("Pear"));
	// FruitMap == [
	// 	{ Key:5, Value:"Banana"    },
	// 	{ Key:2, Value:"Pear"      },
	// 	{ Key:7, Value:"Pineapple" }
	// ]

    //Add函数可接受不带值的键,调用Add函数时其本身被重载,值将被默认构造
    FruitMap.Add(4);
	// FruitMap == [
	// 	{ Key:5, Value:"Banana"    },
	// 	{ Key:2, Value:"Pear"      },
	// 	{ Key:7, Value:"Pineapple" },
	// 	{ Key:4, Value:""          }
	// ]

    //TMap和TArray大致一样,其也可使用Emplace代替Add,防止插入映射时创建临时文件
    //此处直接将键和值传递给了各自的构造函数,其对int32键实际上没有影响,但避免了为该值创建临时FString
    FruitMap.Emplace(3, TEXT("Orange"));
	// FruitMap == [
	// 	{ Key:5, Value:"Banana"    },
	// 	{ Key:2, Value:"Pear"      },
	// 	{ Key:7, Value:"Pineapple" },
	// 	{ Key:4, Value:""          },
	// 	{ Key:3, Value:"Orange"    }
	// ]

    TMap<int32, FString> FruitMap2;
	FruitMap2.Emplace(4, TEXT("Kiwi"));
	FruitMap2.Emplace(9, TEXT("Melon"));
	FruitMap2.Emplace(5, TEXT("Mango"));
	FruitMap.Append(FruitMap2);
    //可使用Apppend函数合并映射,将一个映射的所有元素移至到另一个映射
    //简单点说就是将FruitMap2键值对覆盖FruitMap数组所对应的键值对,然后FruitMap2数组销毁
	// FruitMap == [
	// 	{ Key:5, Value:"Mango"     },
	// 	{ Key:2, Value:"Pear"      },
	// 	{ Key:7, Value:"Pineapple" },
	// 	{ Key:4, Value:"Kiwi"      },
	// 	{ Key:3, Value:"Orange"    },
	// 	{ Key:9, Value:"Melon"     }
	// ]
	// FruitMap2 is now empty.

    //用TMultiMap定义在添加元素的时候可以重叠添加,其元素不会被覆盖
    TMultiMap<int32,FString>FruitMultiMap;
    FruitMultiMap.Add(2, TEXT("Banana"));
	FruitMultiMap.Add(2, TEXT("Grapefruit"));
	FruitMultiMap.Add(2, TEXT("Pineapple"));
}

注意:在上述Add函数示例代码中元素按顺序插入,但不保证这些元素在内存中实际保留此排序。如果是新的映射,可能会保留插入排序,但插入和删除的次数越多,新元素不出现在末尾的可能性就越大。

2.迭代

Actor类头文件增添代码:

public:
    UFUNCTION(BlueprintCallable)
    void IterateMap();

源文件函数代码:

void AMapActor::IterateMap()
{
    TMap<int32,FString>FruitMap;
    FruitMap.Add(1, TEXT("GrapeFruit"));
	FruitMap.Add(2, TEXT("Pineapple"));
	FruitMap.Add(3, TEXT("Melon"));

    for (auto& Elem :FruitMap)//此方法用auto自动类型
	{
  FPlatformMisc::LocalPrint(*FString::Printf(TEXT("Auto-(%d,\"%s\")\n"),Elem.Key,*Elem.Value));
	}

    //第二种写法(可使用C++的设置范围功能,注意元素类型是 TPair)
    for (const TPair<int32,FString>& Element :FruitMap)
	{
        FString Message=FString::Printf(TEXT("TPair-(%d,\"%s\")\n"),Element.Key,*Element.Value));
        FPlatformMisc::LocalPrint(*Message);
	}

    for (auto It = FruitMap.CreateConstIterator(); It; ++It)
	{
FPlatformMisc::LocalPrint(*FString::Printf(TEXT(“Iterator-(%d,\"%s\")\n"),It.Key(),*It.Value()));
	}
}

注意:TMap是无序的,因此不建议在此容器做序列的事情

可以用 CreateIterator 和 CreateConstIterators 函数来创建迭代器。CreateIterator 返回拥有读写访问权限的迭代器,而 CreateConstIterator 返回拥有只读访问权限的迭代器。

3.查询

Actor类头文件增添代码:

public:
    UFUNCTION(BlueprintCallable)
    void QueryMap();

    UFUNCTION(BlueprintCallable)
    void FindMap();

    UFUNCTION(BlueprintCallable)
    void FindAdvMap();

源文件函数代码:

void AMapActor::QueryMap()
{
    TMap<int32,FString>FruitMap;
    FruitMap.Add(5, TEXT("GrapeFruit"));
	FruitMap.Add(7, TEXT("Pineapple"));
	FruitMap.Add(9, TEXT("Melon"));

    //调用 Num 函数即可查询映射中保存的元素数量
    int32 Count = FruitMap.Num();
	// Count == 3

    //确定映射是否包含特定键
    bool bHas7 = FruitMap.Contains(7);
	bool bHas8 = FruitMap.Contains(8);
	// bHas7 == true
	// bHas8 == false

    //如果知道映射中存在某个特定键,可使用 运算符[] 查找相应值,将键用作索引。
    //使用非常量映射执行该操作将返回非常量引用,使用常量映射将返回常量引用。
    FString Val7 = FruitMap[7];
	// Val7 == "Pineapple"
	FString Val8 = FruitMap[8];
	// Assert!此处会触发断言
}


void AMapActor::FindMap()
{
    TMap<int32,FString>FruitMap;

    FString* Ptr7 = FruitMap.Find(7);

    FruitMap.Add(5, TEXT("GrapeFruit"));
	FruitMap.Add(7, TEXT("Pineapple"));
	FruitMap.Add(9, TEXT("Melon"));

    //使用 Find 函数查找一次即可完成判断映射是否包含特定键与键所包含的值。如果映射包含该键,Find 将返回指向元素数值的指针。
    FString* Ptr7 = FruitMap.Find(7);
	FString* Ptr8 = FruitMap.Find(8);
	// *Ptr7 == "Pineapple"
	//  Ptr8 == nullptr
    //如果映射不包含该键,则返回null。在常量映射上调用 Find,返回的指针也将为常量。可以用此特性于if语句中进行日志判断输出其是否为空
    /*if(FString* Ptr7Test = FruitMap.Find(7)
      {
          UE_LOG(LogTemp,Warning,TEXT("%d-%s"),7,**Ptr7Test);//注意此处用两次指针
    /*}
}


void AMapActor::FindAdvMap()
{
    TMap<int32,FString>FruitMap;
    FruitMap.Add(5, TEXT("GrapeFruit"));
	FruitMap.Add(7, TEXT("Pineapple"));
	FruitMap.Add(9, TEXT("Melon"));

    FString& Ref7 = FruitMap.FindOrAdd(7);
    // Ref7     == "Pineapple"
	FString& Ref8 = FruitMap.FindOrAdd(8);
    // Ref8     ==  Empty且FruitMap数组增添了新的键值对键8==Empty
    Ref8=TEXT("NewAdd8");
    //引用修改FruitMap数组中的键8所对应的值    

	FString Val7 = FruitMap.FindRef(7);
    Val7+=TEXT("998");
    //此处只是对Val7进行修改,但对FruitMap数组不会修改

    //不会创建新的元素,如果没有就构建一个临时的默认值返回,返回的是值的副本
	FString Val6 = FruitMap.FindRef(6);
    //值未找到给定键,则返回默认构建值

}

注意:在使用 运算符[] 前,应检查映射中是否存在该键。如果映射中键不存在,将触发断言。

 FindOrAdd 返回对与给定键关联的值的引用

FindRef 会返回与给定键关联的值副本

注意两者的区别:

如果映射中不存在该键,FindOrAdd 将返回新创建的元素(使用给定键和默认构建值),该元素也会被添加到映射。FindOrAdd 可修改映射,因此仅适用于非常量映射。

FindRef 中,若映射中未找到给定键,则返回默认构建值FindRef 不会创建新元素,因此既可用于常量映射,也可用于非常量映射。

即使在映射中找不到键,FindOrAdd 和 FindRef 也会成功运行,因此无需执行常规的安全规程(如提前检查 Contains 或对返回值进行空白检查)就可安全地调用。

警告:示例中初始化 Ref8 时一样,FindOrAdd 可向映射添加新条目,因此之前获得的指针(来自 Find)或引用(来自 FindOrAdd)可能会无效。如果映射的后端存储需要扩展以容纳新元素,会执行分配内存和移动现有数据的添加操作,从而导致这一结果。以上示例中,在调用 FindOrAdd(8) 之后,Ref7 可能会紧随 Ref8 失效

Actor类头文件增添代码:

public:
    UFUNCTION(BlueprintCallable)
    void FindKeyMap();

    UFUNCTION(BlueprintCallable)
    void GetAllKeysAndValueMap();

源文件函数代码:

void AMapActor::FindKeyMap()
{
    TMap<int32,FString>FruitMap;
    FruitMap.Add(5, TEXT("Mango"));
	FruitMap.Add(7, TEXT("Pineapple"));
	FruitMap.Add(10, TEXT("Pineapple"));

    const int32* KeyMangoPtr   = FruitMap.FindKey(TEXT("Mango"));
	const int32* KeyKumquatPtr = FruitMap.FindKey(TEXT("Kumquat"));
	// *KeyMangoPtr   == 5  指针指向
	//  KeyKumquatPtr == nullptr

    const int32* KeyFindTest1 = FruitMap.FindKey(TEXT("Pineapple"));
	// *KeyFindTest1   == 7
    //此处用于触发扩容测试
	FruitMap.Add(11, TEXT("Pineapple"));
	FruitMap.Add(12, TEXT("Pineapple"));
	FruitMap.Add(13, TEXT("Pineapple"));
    const int32* KeyFindTest2 = FruitMap.FindKey(TEXT("Pineapple"));
	// *KeyFindTest1   == 7 此处不一定为7有可能会改变因为其为无序的
}


void AMapActor::GetAllKeysAndValueMap()
{
    TMap<int32,FString>FruitMap;
    FruitMap.Add(5, TEXT("Mango"));
	FruitMap.Add(7, TEXT("Pineapple"));
	FruitMap.Add(10, TEXT("Pineapple"));

    TArray<int32>   FruitKeys;
	TArray<FString> FruitValues;

    FruitMap.GenerateKeyArray  (FruitKeys);
	FruitMap.GenerateValueArray(FruitValues);
}

FindKey 函数执行逆向查找,这意味着提供的值与键匹配,并返回指向与所提供值配对的第一个键的指针。搜索映射中不存在的值将返回空键。

GenerateKeyArray 和 GenerateValueArray 分别使用所有键和值的副本来填充 TArray。在这两种情况下,都会在填充前清空所传递的数组,因此产生的元素数量始终等于映射中的元素数量。

小知识:按值查找比按键查找慢(线性时间)。这是因为映射是根据键而不是值进行哈希。此外,如果映射有多个具有相同值的键,FindKey 可返回其中任一键(可以理解FindKey函数叫Find Value Key)。

4.移除

Actor类头文件增添代码:

public:
    UFUNCTION(BlueprintCallable,Category="Name")
    void RemoveMap();

    UFUNCTION(BlueprintCallable,Category="Name")
    void RemoveCheckMap();

    UFUNCTION(BlueprintCallable,Category="Name")
    void RemoveAndCopyValueMap();

    UFUNCTION(BlueprintCallable,Category="Name")
    void EmptyMap();

    UFUNCTION(BlueprintCallable,Category="Name")
    void ResetMap();

    UFUNCTION(BlueprintCallable,Category="Name")
    void ReserveMap();

源文件函数代码:

void AMapActor::RemoveMap()
{
    TMap<int32,FString>FruitMap;
    FruitMap.Add(5, TEXT("Mango"));
	FruitMap.Add(7, TEXT("Pineapple"));
	FruitMap.Add(10, TEXT("Grapefruit"));

//从映射中移除元素的方法是使用 Remove 函数并提供要移除元素的键。返回值是被移除元素的数量。如果映射不包含与键匹配的元素,则返回值可为零。
    int32 Key5toTemove=FruitMap.Remove(5);//此处返回的为移除的数量,不是被移除的索引
    int32 Key5toTemove=FruitMap.Remove(5);
    //注意:移除元素将在数据结构(在Visual Studio的观察窗口中可视化映射时可看到)中留下slack量
}


void AMapActor::RemoveCheckMap()
{
    TMap<int32,FString>FruitMap;
    FruitMap.Add(5, TEXT("Mango"));
	FruitMap.Add(7, TEXT("Pineapple"));
	FruitMap.Add(10, TEXT("Grapefruit"));

    //FindAndRemoveChecked 函数可用于从映射移除元素并返回其值。
    FString Removed5 = FruitMap.FindAndRemoveChecked(5);
    //名称的"已检查"部分表示若键不存在,映射将调用 check(UE4中等同于 assert)引用断言报错。
	FString Removed8 = FruitMap.FindAndRemoveChecked(8);

}


void AMapActor::RemoveAndCopyValueMap()
{
    TMap<int32,FString>FruitMap;
    FruitMap.Add(5, TEXT("Mango"));
	FruitMap.Add(7, TEXT("Pineapple"));
	FruitMap.Add(10, TEXT("Grapefruit"));

    FString Removed=TEXT("Name");//初始化一个值
	bool bFound5 = FruitMap.RemoveAndCopyValue(5, Removed);
    //将原来的键值对移除,并将已移除元素的值复制到引用参数(如Removed)

	bool bFound8 = FruitMap.RemoveAndCopyValue(8, Removed);
    //如果映射中不存在指定的键,则输出参数将保持不变,函数将返回 false
}


void AMapActor::EmptyMap()
{
    TMap<int32,FString>FruitMap;
    FruitMap.Add(5, TEXT("Mango"));
	FruitMap.Add(7, TEXT("Pineapple"));
	FruitMap.Add(10, TEXT("Grapefruit"));

    FruitMap.Empty(0);//Num=Empty;Max=Empty
    FruitMap.Empty(10);//Num=Empty;Max=10,容量没有被释放,执行后为10个slake元素,原元素被清空
}


void AMapActor::ResetMap()
{
    TMap<int32,FString>FruitMap1;
    FruitMap1.Add(5, TEXT("Mango"));
	FruitMap1.Add(7, TEXT("Pineapple"));
	FruitMap1.Add(10, TEXT("Grapefruit"));

    FruitMap1.Reset(0);//Num=Empty;Max=4,容量没有被释放,执行后为4个slake元素,原元素被清空

    TMap<int32,FString>FruitMap2;
    FruitMap2.Add(5, TEXT("Mango"));
	FruitMap2.Add(7, TEXT("Pineapple"));
	FruitMap2.Add(10, TEXT("Grapefruit"));
	FruitMap2.Add(11, TEXT("Grapefruit"));
	FruitMap2.Add(12, TEXT("Grapefruit"));

    FruitMap2.Reset(0);//Num=Empty;Max=22(此处扩容了)
}

//Reserve函数判断数组能否装下元素,如果可以装下就不会扩容,否则触发扩容
//源码解释:Preallocates enough memory to contain Number elements
void AMapActor::ReserveMap()
{
    TMap<int32,FString>FruitMap;

    FruitMap.Reserve(3);

    FruitMap.Add(5, TEXT("Mango"));
	FruitMap.Add(7, TEXT("Pineapple"));
	FruitMap.Add(10, TEXT("Grapefruit"));

    FruitMap.Reserve(10);//预分配10个元素位置
    FruitMap.Reserve(2);
}

 Empty 和 Reset 相似,但 Empty 可采用参数指示映射中保留的slack量,而 Reset 则是尽可能多地留出slack量。

5.排序

注意:因为TMap容器为无序,因此无法在蓝图中进行排序去loop循环,但在C++层面可以对其进行排序。排序后,迭代映射会以排序的顺序显示元素,但下次修改映射时,排序可能会发生变化。排序是不稳定的,因此等值元素在MultiMap中可能以任何顺序出现。

使用 KeySort 或 ValueSort 函数可分别按键和值进行排序。两个函数均使用二元谓词来进行排序

Actor类头文件增添代码:

public:
    UFUNCTION(BlueprintCallable,Category="Name")
    void SortMap();

源文件函数代码:

void AMapActor::SortMap()
{
    TMap<int32,FString>FruitMap;
	FruitMap.Add(7, TEXT("Pineapple"));
    FruitMap.Add(5, TEXT("Mango"));
	FruitMap.Add(10, TEXT("Grapefruit"));
	FruitMap.Add(1, TEXT("Apple"));

    FString *MyFruit=FruitMap1.Find(10);
    if(MyFruit)
    {
        (*MyFruit)+=("M1");
    }

    FruitMap.KeySort([](int32 A, int32 B) {
		return A > B; // 通过比较键值对的键进行排序,大的在前
	});
	
 
	FruitMap.ValueSort([](const FString& A, const FString& B) {
		return A.Len() < B.Len(); // 比较字符串长度,字符串短的在前
	});

    (*MyFruit)+=("M2");//通过查看可以了解到M2字符串并没有添加到键10处,由此可以验证修改映射时,排序可能会发生变化。排序是不稳定的
}

6.运算符

TMap 是常规值类型,可通过标准复制构造函数赋值运算符进行复制。因为映射严格拥有其元素,复制映射的操作是深层的,所以新的映射将拥有其自己的元素副本。

Actor类头文件增添代码:

public:
    UFUNCTION(BlueprintCallable,Category="Name")
    void OperateMap();

源文件函数代码:

void AMapActor::OperateMap()
{
    TMap<int32,FString>FruitMap;
	FruitMap.Add(7, TEXT("Pineapple"));
    FruitMap.Add(5, TEXT("Mango"));
	FruitMap.Add(10, TEXT("Grapefruit"));
	FruitMap.Add(1, TEXT("Apple"));

	TMap<int32, FString> NewMap = FruitMap;//进行拷贝
	NewMap[5] = "Apple";//将键5的键值对元素进行重构(注意不是索引5)
	NewMap.Remove(1);//移除NewMap数组键1的元素,但其内存空间并未清除

    TMap<int32, FString> NewMap2 = MoveTemp(FruitMap);
    //将FruitMap数组全部键值对移动到NewMap2数组,从而FruitMap数组为Empty

    //扩展知识
    TMap<int32,AActor*>MyActorPtrs;
    MyActorPtrs.Add(1,this);//添加键值对
    MyActorPtrs.Add(2,nullptr);
    MyActorPtrs.Add(3,nullptr);
    TMap<int32,AActor*>MyAnotherActorPtrs=MyActorPtrs;
    MyAnotherActorPtrs[1]->SetActorLocation(FVector::ZeroVector);
    MyActorPtrs[1]->SetActorLocation(FVector::ZeroVector);
    bool bEqual=MyAnotherActorPtrs[1]==MyActorPtrs[0];
}

7.Slack 

Slack是不包含元素的已分配内存。调用 Reserve 可分配内存,无需添加元素;通过非零slack参数调用 Reset 或 Empty 可移除元素,无需将其使用的内存取消分配。Slack优化了将新元素添加到映射的过程,因为可以使用预先分配的内存,而不必分配新内存。它在移除元素时也十分实用,因为系统不需要将内存取消分配。在清空希望用相同或更少的元素立即重新填充的映射时,此方法尤其有效。

Actor类头文件增添代码:

public:
    UFUNCTION(BlueprintCallable,Category="Name")
    void SlackMap();

源文件函数代码:

void AMapActor::SlackMap()
{
    TMap<int32,FString>FruitMap;
    FruitMap.Reserve(10);//尝试提前添加好内存空间
	FruitMap.Add(1, TEXT("A"));
	FruitMap.Add(2, TEXT("B"));
	FruitMap.Add(3, TEXT("C"));
	FruitMap.Add(4, TEXT("D"));
	FruitMap.Add(5, TEXT("E"));
	FruitMap.Add(6, TEXT("F"));
	FruitMap.Add(7, TEXT("G"));
	FruitMap.Add(8, TEXT("H"));
	FruitMap.Add(9, TEXT("I"));
	FruitMap.Add(10, TEXT("J"));

    FruitMap.Remove(2);
    FruitMap.Remove(4);
    FruitMap.Remove(6);
    FruitMap.Remove(8);
    FruitMap.Remove(10);

    FruitMap.Shrink();//移除末尾slack内存空间,注意中间的slack内存空间无法被删除
    FruitMap.Compact();//压缩内存结构,将中间的slack内存空以至尾部
    FruitMap.Shrink();//再次将尾部slack内存空间清除
}

注意:

TMap 不像 TArray 中的 Max 函数那样可以检查预分配元素的数量。

8.结构体的重载

Actor类头文件增添代码:

struct FNameMapInfo
{
    GENERTED_BODY()
    int32 Health=-1;
    FString NPCName=TEXT("None");

    FNameMapInfo()
    {
    }//默认构造函数
    FNameMapInfo(FString InNPCName):NPCName(InNPCName)
    {
    }

    bool operator==(const FNameMapInfo& other)const
    {
        return NPCName==Other.NPCName ? true:false;
    }
    friend uint32 GetTypeHash(const FNameMapInfo& other)
    {
        return GetTypeHash(Other.NPCName);
    }
};


public:
    UFUNCTION(BlueprintCallable,Category="Name")
    void StructMap();

源文件函数代码:

void AMapActor::StructMap()
{
    TMap<FNameMapInfo,int32>MyNPCs;
    MyNPCs.Add(FNameMapInfo(TEXT("Boss")),1);
    MyNPCs.Add(FNameMapInfo(TEXT("Player")),2);
    MyNPCs.Add(FNameMapInfo(TEXT("Tree")),1);
    MyNPCs.Add(FNameMapInfo(TEXT("player")),3);//此处用来测试对大小写是否敏感;此时容器MyNPCs(Num=3,Max=4),且player数量为3(覆盖重构)
    //可以用哈希进行大小写比对算法

    //保证由10个敌人
    MyNPCs.FindOrAdd(FNameMapInfo(TEXT("NormalEnemy")),10);
    //保证至少有4个玩家
    int32& PlayerNum=MyNPCs.FindOrAdd(FNameMapInfo(TEXT("Player")));
}

9.KeyFunces

此内容大概概括:将键值对进行定义,然后再对键和值分别定义和调用

Actor类头文件增添代码:

struct FMyStruct
{
    // String which identifies our key
	FString UniqueID;//此写法会默认构造,用户可自定义初始化
 
	// Some state which doesn't affect struct identity
	float SomeFloat;//此写法会默认构造,用户可自定义初始化

    //explicit如果删除掉在调用结构体时可以FMyStruct MyStruct=0.f隐式转换,不建议这样做
    explicit FMyStruct(float InFloat)
        : UniqueID (FGuid::NewGuid().ToString())//FGuid作为全局唯一的标识符,其可生成int32位的数字
		, SomeFloat(InFloat)
	{
	}
};

template <typename ValueType>

//最后的FString用作计算键的类型,其是真正用作相等性判断和哈希计算
struct TMyStructMapKeyFuncs :BaseKeyFuncs<TPair<FMyStruct, ValueType>,FString>
{
private:
    typedef BaseKeyFuncs<TPair<FMyStruct, ValueType>,FString> Super;
    //<FMyStruct, ValueType>键值对,FMyStruct为KeyType,ValueType是泛型可以存放任何类型元素
public:
	typedef typename Super::ElementInitType ElementInitType;//定义,且后ElementInitType指TPair
	typedef typename Super::KeyInitType     KeyInitType;//后 KeyInitType为FString

    static KeyInitType GetSetKey(ElementInitType Element)//获取键值时传的是TPair,其Key为结构体
	{
		return Element.Key.UniqueID;//返回FString
	}

	static bool Matches(KeyInitType A, KeyInitType B)//参与进去的值作为键值进行判断是否相等
	{
		return A.Compare(B, ESearchCase::CaseSensitive) == 0;//对大小写敏感
	}

	static uint32 GetKeyHash(KeyInitType Key)
	{
		return FCrc::StrCrc32(*Key);//生成唯一的哈希校验码
	}
};


public:
    UFUNCTION(BlueprintCallable,Category="Name")
    void StructKeyFunMap();

源文件函数代码:

void AMapActor::StructKeyFunMap()
{
    TMap<FMyStruct,int32,FDefaultSetAllocator,TMyStructMapKeyFuncs<int32>> MyMapToInt32;
    MyMapToInt32.Add(FMyStruct(3.14f), 5);
	MyMapToInt32.Add(FMyStruct(1.23f), 2);

    TMap<FMyStruct,int32,FDefaultSetAllocator,TMyStructMapKeyFuncs<float>> MyMapToFloat;
    MyMapToFloat.Add(FMyStruct(3.14f), 50.f);
	MyMapToFloat.Add(FMyStruct(1.23f), 20.f);
}

 10.其他

GetAllocatedSize函数用于估计内部数组的当前使用情况。

GetAllocatedSize函数不会接受Farchive参数,其常用于统计报告。

Actor类头文件增添代码:

public:
    UFUNCTION(BlueprintCallable,Category="Name")
    void StructSize();

源文件函数代码:

void AMapActor::StructSize()
{
    TMap<int32,FString>MyMap;
    MyMap.Reserve(4);//分配内存
    uint32 MapSize2=MyMap.GetAllocatedSize();
    MyMap.Add(1,TEXT("A"));
    MyMap.Add(2,TEXT("A"));
    MyMap.Add(3,TEXT("A"));

    uint32 MapSize3=MyMap.GetAllocatedSize();
    MyMap.Add(4,TEXT("A"));
    MyMap.Add(5,TEXT("A"));
    MyMap.Add(6,TEXT("A"));

    uint32 MapSize4=MyMap.GetAllocatedSize();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值