Choose your operating system:
Windows
macOS
Linux
继 TArray
之后,虚幻引擎4(UE4)中最常用的容器是 TMap
。TMap
与 TSet
类似,它们的结构均基于对键进行散列运算。但与 TSet
不同的是,此容器将数据存储为键值对(TPair<KeyType, ValueType>
),只将键用于存储和获取。
映射有两种类型:TMap
和 TMultiMap
。两者之间的不同点是,TMap
中的键是唯一的,而`TMultiMap 可存储多个相同的键。在
TMap 中添加新的键值时,若所用的键与原有的对相同,新对将替换原有的对。在
TMultiMap` 中,容器可以同时存储新对和原有的对。
TMap
在 TMap
中,键值对被视为映射的元素类型,相当于每一对都是个体对象。在本文中,元素就意味着键值对,而各个组件就被称作元素的键或元素的值。元素类型实际上是 TPair<KeyType, ElementType>
,但很少需要直接引用 TPair
类型。
和 TArray
一样,TMap
也是同质容器,就是说它所有元素的类型都应完全相同。TMap
也是值类型,支持通常的复制、赋值和析构函数运算,以及它的元素的强所有权。在映射被销毁时,它的元素都会被销毁。键和值也必须为值类型。
TMap
是散列容器,这意味着键类型必须支持 GetTypeHash
函数,并提供 运算符==
来比较各个键是否等值。稍后将详细介绍散列。
TMap
也可使用任选分配器来控制内存分配行为。但不同于 TArray
,这些是集合分配器,而不是 FHeapAllocator
和 TInlineAllocator
之类的标准UE4分配器。集合分配器(`TSetAllocator`类)定义映射应使用的散列桶数量,以及应使用哪个标准UE4分配器来存储散列和元素。
KeyFuncs
是最后一个 TMap
模板参数,该参数告知映射如何从元素类型获取键,如何比较两个键是否相等,以及如何对键进行散列计算。这些参数有默认值,它们只会返回对键的引用,使用 运算符==
确定相等性,并调用非成员 GetTypeHash
函数进行散列计算。如果您的键类型支持这些函数,可使用它作为映射键,不需要提供自定义 KeyFuncs
。
与 TArray
不同的是,内存中 TMap
元素的相对排序既不可靠也不稳定,对这些元素进行迭代很可能会使它们返回的顺序和它们添加的顺序有所不同。这些元素也不太可能在内存中连续排列。映射的支持数据结构是稀疏数组,这种数组可有效支持元素之间的空位。当元素从映射中被移除时,稀疏数组中就会出现空位。将新的元素添加到数组可填补这些空位。但是,即便 TMap
不会打乱元素来填补空位,指向映射元素的指针仍然可能失效,因为如果存储器被填满,又添加了新的元素,整个存储可能会重新分配。
创建和填充映射
TMap
的创建方法如下:
TMap<int32, FString> FruitMap;
FruitMap
现在是一个字符串的空 TMap
,该字符串由整数键标识。我们既没有指定分配器,也没有指定 KeyFuncs
,所以映射将执行标准的堆分配,使用 运算符==
对键进行对比(int32
类型),并使用 GetTypeHash
进行散列运算。此时没有分配任何内存。
填充映射的标准方法是调用带一个键和值的 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" }
// ]
此处的元素按插入顺序排列,但不保证这些元素在内存中实际保留此排序。如果是新的映射,可能会保留插入排序,但插入和删除的次数越多,新元素不出现在末尾的可能性就越大。
这不是 TMultiMap
,所以各个键都必定是唯一。如果尝试添加重复键,将发生以下情况:
FruitMap.Add(2, TEXT("Pear"));
// FruitMap == [
// { Key:5, Value:"Banana" },
// { Key:2, Value:"Pear" },
// { Key:7, Value:"Pineapple" }
// ]
映射仍然包含3个元素,但之前键值为2的"Grapefruit"已被"Pear"替代。
Add
函数可接受不带值的键。调用此重载后的 Add
时,值将被默认构建:
FruitMap.Add(4);
// FruitMap == [
// { Key:5, Value:"Banana" },
// { Key:2, Value:"Pear" },
// { Key:7, Value:"Pineapple" },
// { Key:4, Value:"" }
// ]
和 TArray
一样,还可使用 Emplace
代替 Add
,防止插入映射时创建临时文件:
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" }
// ]
此处直接将键和值传递给了各自的构造函数。这对 int32
键实际上没有影响,但避免了为该值创建临时 FString
。与 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" }
// ]
// FruitMap2 is now empty.
在上面的示例中,生成的映射和使用 Add
或 Emplace
逐个添加 FruitMap2
的元素相同,在该过程完成时会清空 FruitMap2
。这意味着如果 FruitMap2
中任何元素的键与 FruitMap
中原有元素的键相同,就会取代该元素。
如果用 UPROPERTY
宏和一个可编辑的关键词(EditAnywhere
、EditDefaultsOnly
或 EditInstanceOnly
)标记 TMap
,即可在编辑器中添加和编辑元素。
UPROPERTY(Category = MapsAndSets, EditAnywhere)
TMap<int32, FString> FruitMap;
迭代
TMaps
的迭代类似于 TArrays
。可使用C++的设置范围功能,注意元素类型是 TPair
:
for (auto& Elem :FruitMap)
{
FPlatformMisc::LocalPrint(
*FString::Printf(
TEXT("(%d, \"%s\")\n"),
Elem.Key,
*Elem.Value
)
);
}
// Output:
// (5, "Mango")
// (2, "Pear")
// (7, "Pineapple")
// (4, "Kiwi")
// (3, "Orange")
// (9, "Melon")
也可以用 CreateIterator
和 CreateConstIterators
函数来创建迭代器。CreateIterator
返回拥有读写访问权限的迭代器,而 CreateConstIterator
返回拥有只读访问权限的迭代器。无论哪种情况,均可用这些迭代器的 Key
和 Value
来检查元素。使用迭代器显示"fruit"范例映射将产生如下结果:
for (auto It = FruitMap.CreateConstIterator(); It; ++It)
{
FPlatformMisc::LocalPrint(
*FString::Printf(
TEXT("(%d, \"%s\")\n"),
It.Key(), // same as It->Key
*It.Value() // same as *It->Value
)
);
}
查询
调用 Num
函数即可查询映射中保存的元素数量:
int32 Count = FruitMap.Num();
// Count == 6
要确定映射是否包含特定键,可按下方所示调用 Contains
函数:
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!
如果不确定映射中是否包含某个键,可使用 Contains
函数和 运算符[]
进行检查。但这并非理想的方法,因为同一键需要进行两次查找才能获取成功。使用 Find
函数查找一次即可完成这些行为。如果映射包含该键,Find
将返回指向元素数值的指针。如果映射不包含该键,则返回null。在常量映射上调用 Find
,返回的指针也将为常量。
FString* Ptr7 = FruitMap.Find(7);
FString* Ptr8 = FruitMap.Find(8);
// *Ptr7 == "Pineapple"
// Ptr8 == nullptr
或为了确保查询的结果有效,可使用 FindOrAdd
或 FindRef
。FindOrAdd
将返回对与给定键关联的值的引用。如果映射中不存在该键,FindOrAdd
将返回新创建的元素(使用给定键和默认构建值),该元素也会被添加到映射。FindOrAdd
可修改映射,因此仅适用于非常量映射。不要被名称迷惑,FindRef
会返回与给定键关联的值副本;若映射中未找到给定键,则返回默认构建值。FindRef
不会创建新元素,因此既可用于常量映射,也可用于非常量映射。即使在映射中找不到键,FindOrAdd
和 FindRef
也会成功运行,因此无需执行常规的安全规程(如提前检查 Contains
或对返回值进行空白检查)就可安全地调用。
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 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:"" }
// ]
和示例中初始化 Ref8
时一样,FindOrAdd
可向映射添加新条目,因此之前获得的指针(来自 Find
)或引用(来自 FindOrAdd
)可能会无效。如果映射的后端存储需要扩展以容纳新元素,会执行分配内存和移动现有数据的添加操作,从而导致这一结果。以上示例中,在调用 FindOrAdd(8)
之后,Ref7
可能会紧随 Ref8
失效。
FindKey
函数执行逆向查找,这意味着提供的值与键匹配,并返回指向与所提供值配对的第一个键的指针。搜索映射中不存在的值将返回空键。
const int32* KeyMangoPtr = FruitMap.FindKey(TEXT("Mango"));
const int32* KeyKumquatPtr = FruitMap.FindKey(TEXT("Kumquat"));
// *KeyMangoPtr == 5
// KeyKumquatPtr == nullptr
按值查找比按键查找慢(线性时间)。这是因为映射按键排序,而非按值排序。此外,如果映射有多个具有相同值的键,FindKey
可返回其中任一键。
GenerateKeyArray
和 GenerateValueArray
分别使用所有键和值的副本来填充 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","" ]
移除
从映射中移除元素的方法是使用 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" }
// ]
移除元素将在数据结构(在Visual Studio的观察窗口中可视化映射时可看到)中留下空位,但为保证清晰度,此处省略。
FindAndRemoveChecked
函数可用于从映射移除元素并返回其值。名称的"已检查"部分表示若键不存在,映射将调用 check
(UE4中等同于 assert
)。
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
函数的作用与 Remove
相似,不同点是会将已移除元素的值复制到引用参数。如果映射中不存在指定的键,则输出参数将保持不变,函数将返回 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" }
// ]
最后,使用 Empty
或 Reset
函数可将映射中的所有元素移除。
TMap<int32, FString> FruitMapCopy = FruitMap;
// FruitMapCopy == [
// { Key:5, Value:"Mango" },
// { Key:4, Value:"Kiwi" },
// { Key:3, Value:"Orange" },
// { Key:9, Value:"Melon" }
// ]
FruitMapCopy.Empty(); // We could also have called Reset() here.
// FruitMapCopy == []
Empty
和 Reset
相似,但 Empty
可采用参数指示映射中保留的slack量,而 Reset
排序
TMap
可以进行排序。排序后,迭代映射会以排序的顺序显示元素,但下次修改映射时,排序可能会发生变化。排序是不稳定的,因此等值元素在MultiMap中可能以任何顺序出现。
使用 KeySort
或 ValueSort
函数可分别按键和值进行排序。两个函数均使用二元谓词来进行排序:
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" }
// ]
运算符
和 TArray
一样,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" }
// ]
TMap
支持移动语义,使用 MoveTemp
函数可调用这些语义。在移动后,源映射必定为空:
FruitMap = MoveTemp(NewMap);
// FruitMap == [
// { Key:4, Value:"Kiwi" },
// { Key:5, Value:"Apple" },
// { Key:9, Value:"Melon" }
// ]
// NewMap == []
Slack
Slack是不包含元素的已分配内存。调用 Reserve
可分配内存,无需添加元素;通过非零slack参数调用 Reset
或 Empty
可移除元素,无需将其使用的内存取消分配。Slack优化了将新元素添加到映射的过程,因为可以使用预先分配的内存,而不必分配新内存。它在移除元素时也十分实用,因为系统不需要将内存取消分配。在清空希望用相同或更少的元素立即重新填充的映射时,此方法尤其有效。
TMap
不像 TArray
中的 Max
函数那样可以检查预分配元素的数量。
在下列代码中,Reserve
函数预先分配映射,最多可包含10个元素。
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" }
// ]
使用 Collapse
和 Shrink
函数可移除 TMap
中的全部slack。Shrink
将从容器的末端移除所有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
只删除了一个无效元素,因为末端只有一个空元素。要移除所有slack,首先应调用 Compact
函数,将空白空间组合在一起,为调用 Shrink
做好准备。
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" }
// ]
KeyFuncs
只要类型具有 运算符==
和非成员 GetTypeHash
重载,就可用作 TMap
的键类型,不需要任何更改。但是,您可能需要将类型用作键,而不重载这些函数。在这些情况下,可对 KeyFuncs
进行自定义。为键类型创建 KeyFuncs
,必须定义两个typedef和三个静态函数,如下所示:
KeyInitType
—— 用于传递键的类型。ElementInitType
—— 用于传递元素的类型。KeyInitType GetSetKey(ElementInitType Element)
——返回元素的键。bool Matches(KeyInitType A, KeyInitType B)
—— 如果A
和B
等值将返回true
,否则返回false
。uint32 GetKeyHash(KeyInitType Key)
—— 返回Key
的散列值。
KeyInitType
和 ElementInitType
是键类型和值类型的常规传递约定的typedef。它们通常为浅显类型的一个值,和非浅显类型的一个常量引用。请记住,映射的元素类型是 TPair
。
自定义 KeyFuncs
的示例可能如下所示:
struct FMyStruct
{
// String which identifies our key
FString UniqueID;
// Some state which doesn't affect struct identity
float SomeFloat;
explicit FMyStruct(float InFloat)
:UniqueID (FGuid::NewGuid().ToString())
, SomeFloat(InFloat)
{
}
};
template <typename ValueType>
struct TMyStructMapKeyFuncs :
BaseKeyFuncs<
TPair<FMyStruct, ValueType>,
FString
>
{
private:
typedef BaseKeyFuncs<
TPair<FMyStruct, ValueType>,
FString
> Super;
public:
typedef typename Super::ElementInitType ElementInitType;
typedef typename Super::KeyInitType KeyInitType;
static KeyInitType GetSetKey(ElementInitType Element)
{
return Element.Key.UniqueID;
}
static bool Matches(KeyInitType A, KeyInitType B)
{
return A.Compare(B, ESearchCase::CaseSensitive) == 0;
}
static uint32 GetKeyHash(KeyInitType Key)
{
return FCrc::StrCrc32(*Key);
}
};
FMyStruct
具有唯一标识符,以及一些与身份无关的其他数据。GetTypeHash
和 运算符==
不适用于此,因为 运算符==
为实现通用目的不应忽略任何类型的数据,但同时又需要如此才能与 GetTypeHash
的行为保持一致,后者只关注 UniqueID
字段。以下步骤有助于为 FMyStruct
创建自定义 KeyFuncs
:
首先,继承
BaseKeyFuncs
,因为它可以帮助定义某些类型,包括KeyInitType
和ElementInitType
。BaseKeyFuncs
使用两个模板参数:映射的元素类型和键类型。和所有映射一样,元素类型是TPair
,使用FMyStruct
作为其KeyType
,TMyStructMapKeyFuncs
的模板参数作为其ValueType
。将备用KeyFuncs
用作模板,可为每个映射指定ValueType
,因此每次要在FMyStruct
上创建键控TMap
时不必定义新的KeyFuncs
。第二个BaseKeyFuncs
参数是键类型,不要与元素存储的键区(TPair
的KeyType
)混淆。因为此映射应使用UniqueID
(来自FMyStruct
)作为键,所以此处使用FString
。然后,定义三个必需的
KeyFuncs
静态函数。第一个是GetSetKey
,该函数返回给定元素类型的键。由于元素类型是TPair
,而键是UniqueID
,所以该函数可直接返回UniqueID
。第二个静态函数是
Matches
,该函数接受两个元素的键(由GetSetKey
获取),然后比较它们是否相等。在FString
中,标准的等效测试(运算符==
)不区分大小写;要替换为区分大小写的搜索,请用相应的大小写对比选项使用Compare
函数。最后,
GetKeyHash
静态函数接受提取的键并返回其散列值。由于Matches
函数区分大小写,GetKeyHash
也必须区分大小写。区分大小写的FCrc
函数将计算键字符串的散列值。现在结构已满足
TMap
要求的行为,可创建它的实例。
KeyFuncs
参数处于最后,所以这个 TMap
TMap<
FMyStruct,
int32,
FDefaultSetAllocator,
TMyStructMapKeyFuncs<int32>
> MyMapToInt32;
// Add some elements
MyMapToInt32.Add(FMyStruct(3.14f), 5);
MyMapToInt32.Add(FMyStruct(1.23f), 2);
// MyMapToInt32 == [
// {
// Key:{
// UniqueID:"D06AABBA466CAA4EB62D2F97936274E4",
// SomeFloat:3.14f
// },
// Value:5
// },
// {
// Key:{
// UniqueID:"0661218447650259FD4E33AD6C9C5DCB",
// SomeFloat:1.23f
// },
// Value:5
// }
// ]
TMap
假设两个项目使用 Matches
比较的结果相等,则它们会从 GetKeyHash
返回相同的值。此外,如果对现有映射元素的键进行的修改将会改变来自这两个函数中任一个的结果,那么系统会将这种修改视作未定义的行为,因为这会使映射的内部散列失效。这些规则也适用于使用默认 KeyFuncs
时 运算符==
和 GetKeyHash
其他
CountBytes
和 GetAllocatedSize
函数用于估计内部数组的当前内存使用情况。CountBytes
接受 Farchive
参数,而 GetAllocatedSize
则不会。这些函数常用于统计报告。
Dump
函数接受 FOutputDevice
,并写出关于映射内容的实现信息。此函数常用于调试。