TMap 也是是虚幻引擎中最常用的容器类。与TArray很相似,不过TMap是以键值对<Key, Value>的形式存在的。
1、创建
TMap<int32, FString> FruitMap;
2、填充数据
Add 和 Emplace
填充Map的标准方式是使用 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" }
// ]
虽然这里我们是按(5、2、7)这样的顺序填充的数据,但无法保证这些元素就是按照这样的顺序排列,因为 TMap 是无序的。
TMap 的 Key 是唯一的,当我们添加一个已经存在的 Key 时,它会覆盖原有的 Value :
FruitMap.Add(2, TEXT("Pear"));
// FruitMap == [
// { Key:5, Value:"Banana" },
// { Key:2, Value:"Pear" },
// { Key:7, Value:"Pineapple" }
// ]
TMultiMap 的 Key 不是唯一的,可以多次添加同一个 Key 的 <Key, Value>。
当我们调用 Add 时,只传入参数 Key,不传 Value 的话,会使用 Value 的默认构造函数来创建对象:
FruitMap.Add(4);
// FruitMap == [
// { Key:5, Value:"Banana" },
// { Key:2, Value:"Pear" },
// { Key:7, Value:"Pineapple" },
// { Key:4, Value:"" }
// ]
与 TArray 一样,也可以使用 Emplace 来避免添加过程中创建临时变量:
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" }
// ]
Append
同 TArray 一样,我们可以使用 Append 来一次性添加多个元素:
TMap<int32, FString> FruitMap2;
FruitMap2.Emplace(4, TEXT("Kiwi"));
FruitMap2.Emplace(9, TEXT("Melon"));
FruitMap2.Emplace(5, TEXT("Mango"));
FruitMap.Append(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" }
// ]
相当于使用 Add/Emplace 逐个添加,同样会覆盖已存在的键值对。
3、遍历
遍历 TMap 与遍历 TArray类似。
使用C++的 ranged-for’:
for (auto& Elem : FruitMap)
{
UE_LOG(LogClass, Log, TEXT("Key: %d Value: %s"), Elem.Key, *Elem.Value);
}
// Output:
//Key: 5 Value : Mango
//Key: 2 Value : Pear
//Key: 7 Value : Pineapple
//Key: 4 Value : Kiwi
//Key: 3 Value : Orange
//Key: 9 Value : Melon
使用迭代器 CreateIterator 或 CreateConstIterator :
for (auto It = FruitMap.CreateConstIterator(); It; ++It)
{
UE_LOG(LogClass, Log, TEXT("Key: %d Value: %s"), It.Key(), *It.Value());
}
// Output:
//Key: 5 Value : Mango
//Key: 2 Value : Pear
//Key: 7 Value : Pineapple
//Key: 4 Value : Kiwi
//Key: 3 Value : Orange
//Key: 9 Value : Melon
4、查询相关
Num 函数获取TMap保存的元素数量:
int32 Count = FruitMap.Num();
// Count == 6
与访问TArray类似,我们可以使用Key的索引来获取对应的Value:
FString Val7 = FruitMap[7];
// Val7 == "Pineapple"
//FString Val8 = FruitMap[8]; // assert!
如果对应的 <Key, Value>不存在,则会触发运行时断言。
使用 Contains 函数检查 TMap 中是否存在特定键:
bool bHas7 = FruitMap.Contains(7);
bool bHas8 = FruitMap.Contains(8);
// bHas7 == true
// bHas8 == false
通常我们可以使用 **操作符“[ ]” 或 Contains 函数来查找 TMap 是否存在某个 Key,但这不是最优的,因为它们会对 Key 进行两次查找。而 Find 函数只执行一次查找,返回的是指向元素的指针,若不存在则返回 null :
FString* Ptr7 = FruitMap.Find(7);
FString* Ptr8 = FruitMap.Find(8);
// *Ptr7 == "Pineapple"
// Ptr8 == nullptr
FindOrAdd 函数将会搜索对应的 <Key, Value> 是否存在,如果存在则返回 Value 的引用,如果不存在,则会以默认构造函数添加 <Key, Value>,并返回 Value 的引用:
FString& Ref7 = FruitMap.FindOrAdd(7);
// Ref7 == "Pineapple"
// 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" }
// ]
FString& Ref8 = FruitMap.FindOrAdd(8);
// Ref8 == ""
// 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" },
// { Key:8, Value:"" }
// ]
因为可能需要执行 Add 操作,所以不能对常量TMap调用此函数。另:如果调用 FruitMap.FindOrAdd(8); 时,发生了内存重新分配,则可能导致 引用“Ref7” 失效。
FindRef 函数将会搜索对应的 <Key, Value> 是否存在,如果存在则返回 Value的值(虽然名字是“Ref”,但返回的是值的副本,不是引用),如果不存在,则返回以默认构造函数创建的值,但不会修改 TMap:
FString Val7 = FruitMap.FindRef(7);
FString Val6 = FruitMap.FindRef(6);
// Val7 == "Pineapple"
// Val6 == ""
// 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" },
// { Key:8, Value:"" }
// ]
FindKey 函数是反向查找(即根据 “Value” 查找“Key”),由于Value可能不是唯一的,索引返回的Key可能是任意对应的一个Key:
const int32* KeyMangoPtr = FruitMap.FindKey(TEXT("Mango"));
const int32* KeyKumquatPtr = FruitMap.FindKey(TEXT("Kumquat"));
// *KeyMangoPtr == 5
// KeyKumquatPtr == nullptr
GenerateKeyArray 和 GenerateValueArray 分别是使用所有的 Key 和 Value 生成TArray,这两种情况,都会将TArray之前的数据清空:
TArray<int32> FruitKeys;
TArray<FString> FruitValues;
FruitKeys.Add(999);
FruitKeys.Add(123);
FruitMap.GenerateKeyArray (FruitKeys);
FruitMap.GenerateValueArray(FruitValues);
// FruitKeys == [ 5,2,7,4,3,9,8 ]
// FruitValues == [ "Mango","Pear","Pineapple","Kiwi","Orange","Melon","" ]
5、移除
Remove
FruitMap.Remove(8);
// 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" }
// ]
FindAndRemoveChecked
FindAndRemoveChecked 函数从TMap中移除并返回其值,如果不存在对应的Key,则会触发运行时断言:
FString Removed7 = FruitMap.FindAndRemoveChecked(7);
// Removed7 == "Pineapple"
// FruitMap == [
// { Key:5, Value:"Mango" },
// { Key:2, Value:"Pear" },
// { Key:4, Value:"Kiwi" },
// { Key:3, Value:"Orange" },
// { Key:9, Value:"Melon" }
// ]
//FString Removed8 = FruitMap.FindAndRemoveChecked(8); // assert!
RemoveAndCopyValue
RemoveAndCopyValue 函数会复制移除的元素的Value到传入的参数中,并且返回bool值来表明是否找到对应的键值对,如果没找到则会返回false,不会发生运行时错误:
FString Removed;
bool bFound2 = FruitMap.RemoveAndCopyValue(2, Removed);
// bFound2 == true
// Removed == "Pear"
// FruitMap == [
// { Key:5, Value:"Mango" },
// { Key:4, Value:"Kiwi" },
// { Key:3, Value:"Orange" },
// { Key:9, Value:"Melon" }
// ]
bool bFound8 = FruitMap.RemoveAndCopyValue(8, Removed);
// bFound8 == false
// Removed == "Pear", i.e. unchanged
// FruitMap == [
// { Key:5, Value:"Mango" },
// { Key:4, Value:"Kiwi" },
// { Key:3, Value:"Orange" },
// { Key:9, Value:"Melon" }
// ]
与 TArray 一样,可以使用 Empty 函数来移除所有元素。
6、排序
可以分别使用 KeySort 和 ValueSort 函数来按 Key 或 Value 来进行排序。但TMap的排序是不稳定的,当对TMap修改时很可能会导致TMap的排序被打乱:
FruitMap.KeySort([](int32 A, int32 B) {
return A > B; // sort keys in reverse
});
// FruitMap == [
// { Key:9, Value:"Melon" },
// { Key:5, Value:"Mango" },
// { Key:4, Value:"Kiwi" },
// { Key:3, Value:"Orange" }
// ]
FruitMap.ValueSort([](const FString& A, const FString& B) {
return A.Len() < B.Len(); // sort strings by length
});
// FruitMap == [
// { Key:4, Value:"Kiwi" },
// { Key:5, Value:"Mango" },
// { Key:9, Value:"Melon" },
// { Key:3, Value:"Orange" }
// ]
7、运算符
和TArray一样,TMap也可以通过标准复制构造函数或赋值运算符复制。同样TMap复制也是深度复制,所有新的TMap将拥有其自身的元素副本:
TMap<int32, FString> NewMap = FruitMap;
NewMap[5] = "Apple";
NewMap.Remove(3);
// FruitMap == [
// { Key:4, Value:"Kiwi" },
// { Key:5, Value:"Mango" },
// { Key:9, Value:"Melon" },
// { Key:3, Value:"Orange" }
// ]
// NewMap == [
// { Key:4, Value:"Kiwi" },
// { Key:5, Value:"Apple" },
// { Key:9, Value:"Melon" }
// ]
同样可以使用 MoveTemp 来移动TMap,移动后,源TMap将为空:
FruitMap = MoveTemp(NewMap);
// FruitMap == [
// { Key:4, Value:"Kiwi" },
// { Key:5, Value:"Apple" },
// { Key:9, Value:"Melon" }
// ]
// NewMap == []
8、Slack
与TArray相似,Reset 函数所起的作用类似于Empty()调用,但不会释放元素先前使用的内存:
FruitMap.Reset();
// FruitMap == [<invalid>, <invalid>, <invalid>]
但TMap不像TArray一样可以使用 TArray:Max() 检查给TArray分配了多少内存,但仍支持预分配 Slack,Reserve 函数可用于在添加元素前为TMap分配特定数量的Slack:
FruitMap.Reserve(10);
for (int32 i = 0; i < 10; ++i)
{
FruitMap.Add(i, FString::Printf(TEXT("Fruit%d"), i));
}
// FruitMap == [
// { Key:9, Value:"Fruit9" },
// { Key:8, Value:"Fruit8" },
// ...
// { Key:1, Value:"Fruit1" },
// { Key:0, Value:"Fruit0" }
// ]
与TArray一样,我们可以使用 Shrink 来移除末尾没有使用的 Slack,但由于TMap允许其数据结构中存在空位,所以它只会移除末尾的 Slack:
for (int32 i = 0; i < 10; i += 2)
{
FruitMap.Remove(i);
}
// FruitMap == [
// { Key:9, Value:"Fruit9" },
// <invalid>,
// { Key:7, Value:"Fruit7" },
// <invalid>,
// { Key:5, Value:"Fruit5" },
// <invalid>,
// { Key:3, Value:"Fruit3" },
// <invalid>,
// { Key:1, Value:"Fruit1" },
// <invalid>
// ]
FruitMap.Shrink();
// FruitMap == [
// { Key:9, Value:"Fruit9" },
// <invalid>,
// { Key:7, Value:"Fruit7" },
// <invalid>,
// { Key:5, Value:"Fruit5" },
// <invalid>,
// { Key:3, Value:"Fruit3" },
// <invalid>,
// { Key:1, Value:"Fruit1" }
// ]
上面的 Shrink函数 只移除了一个处于末尾的空位,但我们可以在调用 Shrink 前调用用 Compact 函数来把所有的空位移到末尾,这样我们就可以移除所有的空位了:
FruitMap.Compact();
// FruitMap == [
// { Key:9, Value:"Fruit9" },
// { Key:7, Value:"Fruit7" },
// { Key:5, Value:"Fruit5" },
// { Key:3, Value:"Fruit3" },
// { Key:1, Value:"Fruit1" },
// <invalid>,
// <invalid>,
// <invalid>,
// <invalid>
// ]
FruitMap.Shrink();
// FruitMap == [
// { Key:9, Value:"Fruit9" },
// { Key:7, Value:"Fruit7" },
// { Key:5, Value:"Fruit5" },
// { Key:3, Value:"Fruit3" },
// { Key:1, Value:"Fruit1" }
// ]
9、测试代码
TMap<int32, FString> FruitMap;
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" }
// ]
FruitMap.Add(2, TEXT("Pear"));
// FruitMap == [
// { Key:5, Value:"Banana" },
// { Key:2, Value:"Pear" },
// { Key:7, Value:"Pineapple" }
// ]
FruitMap.Add(4);
// FruitMap == [
// { Key:5, Value:"Banana" },
// { Key:2, Value:"Pear" },
// { Key:7, Value:"Pineapple" },
// { Key:4, Value:"" }
// ]
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);
// 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" }
// ]
for (auto& Elem : FruitMap)
{
UE_LOG(LogClass, Log, TEXT("Key: %d Value: %s"), Elem.Key, *Elem.Value);
}
// Output:
//Key: 5 Value : Mango
//Key: 2 Value : Pear
//Key: 7 Value : Pineapple
//Key: 4 Value : Kiwi
//Key: 3 Value : Orange
//Key: 9 Value : Melon
for (auto It = FruitMap.CreateConstIterator(); It; ++It)
{
UE_LOG(LogClass, Log, TEXT("Key: %d Value: %s"), It.Key(), *It.Value());
}
// Output:
//Key: 5 Value : Mango
//Key: 2 Value : Pear
//Key: 7 Value : Pineapple
//Key: 4 Value : Kiwi
//Key: 3 Value : Orange
//Key: 9 Value : Melon
int32 Count1 = FruitMap.Num();
// Count1 == 6
FString Val7 = FruitMap[7];
// Val7 == "Pineapple"
//FString Val8 = FruitMap[8]; // assert!
bool bHas7 = FruitMap.Contains(7);
bool bHas8 = FruitMap.Contains(8);
// bHas7 == true
// bHas8 == false
FString* Ptr7 = FruitMap.Find(7);
FString* Ptr8 = FruitMap.Find(8);
// *Ptr7 == "Pineapple"
// Ptr8 == nullptr
FString& Ref7 = FruitMap.FindOrAdd(7);
// Ref7 == "Pineapple"
// 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" }
// ]
FString& Ref8 = FruitMap.FindOrAdd(8);
// Ref8 == ""
// 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" },
// { Key:8, Value:"" }
// ]
FString Val71 = FruitMap.FindRef(7);
FString Val6 = FruitMap.FindRef(6);
// Val71 == "Pineapple"
// Val6 == ""
// 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" },
// { Key:8, Value:"" }
// ]
const int32* KeyMangoPtr = FruitMap.FindKey(TEXT("Mango"));
const int32* KeyKumquatPtr = FruitMap.FindKey(TEXT("Kumquat"));
// *KeyMangoPtr == 5
// KeyKumquatPtr == nullptr
TArray<int32> FruitKeys;
TArray<FString> FruitValues;
FruitKeys.Add(999);
FruitKeys.Add(123);
FruitMap.GenerateKeyArray(FruitKeys);
FruitMap.GenerateValueArray(FruitValues);
// FruitKeys == [ 5,2,7,4,3,9,8 ]
// FruitValues == [ "Mango","Pear","Pineapple","Kiwi","Orange","Melon","" ]
FruitMap.Remove(8);
// 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" }
// ]
FString Removed7 = FruitMap.FindAndRemoveChecked(7);
// Removed7 == "Pineapple"
// FruitMap == [
// { Key:5, Value:"Mango" },
// { Key:2, Value:"Pear" },
// { Key:4, Value:"Kiwi" },
// { Key:3, Value:"Orange" },
// { Key:9, Value:"Melon" }
// ]
//FString Removed8 = FruitMap.FindAndRemoveChecked(8); // assert!
FString Removed;
bool bFound2 = FruitMap.RemoveAndCopyValue(2, Removed);
// bFound2 == true
// Removed == "Pear"
// FruitMap == [
// { Key:5, Value:"Mango" },
// { Key:4, Value:"Kiwi" },
// { Key:3, Value:"Orange" },
// { Key:9, Value:"Melon" }
// ]
bool bFound8 = FruitMap.RemoveAndCopyValue(8, Removed);
// bFound8 == false
// Removed == "Pear", i.e. unchanged
// FruitMap == [
// { Key:5, Value:"Mango" },
// { Key:4, Value:"Kiwi" },
// { Key:3, Value:"Orange" },
// { Key:9, Value:"Melon" }
// ]
FruitMap.KeySort([](int32 A, int32 B) {
return A > B; // sort keys in reverse
});
// FruitMap == [
// { Key:9, Value:"Melon" },
// { Key:5, Value:"Mango" },
// { Key:4, Value:"Kiwi" },
// { Key:3, Value:"Orange" }
// ]
FruitMap.ValueSort([](const FString& A, const FString& B) {
return A.Len() < B.Len(); // sort strings by length
});
// FruitMap == [
// { Key:4, Value:"Kiwi" },
// { Key:5, Value:"Mango" },
// { Key:9, Value:"Melon" },
// { Key:3, Value:"Orange" }
// ]
TMap<int32, FString> NewMap = FruitMap;
NewMap[5] = "Apple";
NewMap.Remove(3);
// FruitMap == [
// { Key:4, Value:"Kiwi" },
// { Key:5, Value:"Mango" },
// { Key:9, Value:"Melon" },
// { Key:3, Value:"Orange" }
// ]
// NewMap == [
// { Key:4, Value:"Kiwi" },
// { Key:5, Value:"Apple" },
// { Key:9, Value:"Melon" }
// ]
FruitMap = MoveTemp(NewMap);
// FruitMap == [
// { Key:4, Value:"Kiwi" },
// { Key:5, Value:"Apple" },
// { Key:9, Value:"Melon" }
// ]
// NewMap == []
FruitMap.Reset();
// FruitMap == [<invalid>, <invalid>, <invalid>]
FruitMap.Reserve(10);
for (int32 i = 0; i < 10; ++i)
{
FruitMap.Add(i, FString::Printf(TEXT("Fruit%d"), i));
}
// FruitMap == [
// { Key:9, Value:"Fruit9" },
// { Key:8, Value:"Fruit8" },
// ...
// { Key:1, Value:"Fruit1" },
// { Key:0, Value:"Fruit0" }
// ]
for (int32 i = 0; i < 10; i += 2)
{
FruitMap.Remove(i);
}
// FruitMap == [
// { Key:9, Value:"Fruit9" },
// <invalid>,
// { Key:7, Value:"Fruit7" },
// <invalid>,
// { Key:5, Value:"Fruit5" },
// <invalid>,
// { Key:3, Value:"Fruit3" },
// <invalid>,
// { Key:1, Value:"Fruit1" },
// <invalid>
// ]
FruitMap.Shrink();
// FruitMap == [
// { Key:9, Value:"Fruit9" },
// <invalid>,
// { Key:7, Value:"Fruit7" },
// <invalid>,
// { Key:5, Value:"Fruit5" },
// <invalid>,
// { Key:3, Value:"Fruit3" },
// <invalid>,
// { Key:1, Value:"Fruit1" }
// ]
FruitMap.Compact();
// FruitMap == [
// { Key:9, Value:"Fruit9" },
// { Key:7, Value:"Fruit7" },
// { Key:5, Value:"Fruit5" },
// { Key:3, Value:"Fruit3" },
// { Key:1, Value:"Fruit1" },
// <invalid>,
// <invalid>,
// <invalid>,
// <invalid>
// ]
FruitMap.Shrink();
// FruitMap == [
// { Key:9, Value:"Fruit9" },
// { Key:7, Value:"Fruit7" },
// { Key:5, Value:"Fruit5" },
// { Key:3, Value:"Fruit3" },
// { Key:1, Value:"Fruit1" }
// ]