TMap

TMap主要由两个类型定义(一个键类型和一个值类型),以关联对的形式存储在映射中。

Windows
MacOS
Linux

继TArray之后,在 虚幻引擎4(UE4)中最常用的容器是 TMap。此容器是关联式容器,也就是说,每个键都有一个关联的数值,你可以按照键高效地查找值对象。

映射有两种类型:TMap和 TMultiMap。TMap的键是唯一的,插入新的键值对时,如果该键已经存在,原有的键值对将被替换。TMultiMap的键不是唯一的,因此原有的键值对不会被新添加的键值对替换。

TMap

TMap主要由两个类型定义(一个键类型和一个值类型),以关联对的形式存储在映射中。可以很方便地将这些键值对称为映射的元素类型,就好像它是个体对象一样。在本文中,元素就意味着键值对,而各个组件就被称作元素的键或元素的值。元素类型实际上是TPair< KeyType, ElementType >,不过需要直接引用TPair类型的时候应该很少。

和TArray一样,TMap是同构容器,也就是说它的所有元素的类型都应该完全相同。TMap也是值类型,支持通常的复制、赋值和析构函数运算,以及它的元素的强所有权,在映射被销毁时,它的元素都会被销毁。键类型和值类型也必须是值类型。

TMap是散列容器,这意味着在默认情况下,键类型必须支持GetTypeHash函数,并提供运算符==用于比较各个键是否相等。稍后会更详细地讲述散列。

TMap也可以使用可选的分配器来控制内存分配行为。标准UE4分配器(例如FHeapAllocatorTInlineAllocator)不能作为TMap的分配器使用。它应该使用设置分配器,这类分配器定义映射应该使用多少散列桶,以及应该使用哪个标准UE4分配器来存储散列和元素。请参见TSetAllocator了解更多信息。

最后一个TMap模板参数是 KeyFuncs,它告诉映射如何从元素类型检索键,如何比较两个键是否相等,以及如何对键进行散列计算。这些参数有默认值,它们只会返回对键的引用,使用运算符==确定相等性,并使用非成员GetTypeHash函数进行散列计算。如果你的键类型支持这些函数,可以使用它作为映射键,不需要提供自定义KeyFuncs。

与TArray不同的是,不能依靠TMap元素在内存中的相对顺序,对这些元素进行迭代很可能会使它们返回时的顺序不同于添加它们的顺序。这些元素也不太可能在内存中连续排列。映射的支持数据结构是稀疏阵列,也就是有空位的数组。当元素被从映射移除时,稀疏阵列中就会出现空位。以后添加元素时,又会填补这些空位。但是,即便TMap不会打乱元素来填补空位,指向映射元素的指针仍然可能失效,因为如果存储器被填满,又添加了新的元素,整个存储器可能会重新分配。

创建和填充映射

可以像这样创建TMap:

TMap<int32, FString> 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函数被重载,接受了没有值的键。只要提供一个键,就会默认构造值:

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

在上面的示例中,得到的映射相当于使用Add/Emplace逐个添加元素,使来自源映射的重复键替换目标映射中的键的结果。

如果用UPROPERTY()宏和可编辑的关键字之一(EditAnywhereEditDefaultsOnlyEditInstanceOnly)标记TMap,就可以在虚幻编辑器中添加和编辑元素

UPROPERTY(Category = MapsAndSets, EditAnywhere)
TMap<int32, FString> FruitMap;

迭代

TMap上的迭代与TArray上的很相似。你可以使用C++的ranged-for功能,并且要记住元素类型是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 函数提供对元素的读写访问权,CreateConstIterator 函数提供只读访问权。迭代器对象本身提供 Key()Value() 函数用于键和值访问。你可能会在代码中看到使用任一表单:

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

我们可以使用带键的索引运算符[]来检索与给定键关联的值的引用。对非常量映射调用运算符[]将返回非常量引用,对常量映射调用将会返回常量映射。如果键不存在,那么将会触发断言:

FString Val7 = FruitMap[7];
// Val7 == "Pineapple"
FString Val8 = FruitMap[8]; // assert!

我们可以使用 Contains 函数检查映射中是否存在特定键:

bool bHas7 = FruitMap.Contains(7);
bool bHas8 = FruitMap.Contains(8);
// bHas7 == true
// bHas8 == false

在大多数时候,你会希望在不知道键是否存在的情况下查找元素。使用后跟运算符[]的Contains意味着执行对键的双重查找,这并不是我们真正想做的事。Find 函数可以让你执行单一查找,返回指向找到的元素值的指针,而不是引用,而在键不存在的情况下会返回null:

FString* Ptr7 = FruitMap.Find(7);
FString* Ptr8 = FruitMap.Find(8);
// *Ptr7 == "Pineapple"
//  Ptr8 == nullptr

如果对常量映射调用它,返回的指针也将是常量。

FindOrAdd 函数将会搜索给定键并返回对关联值的引用;如果该键不存在,它将添加该键以及默认构造的值,然后返回对该值的引用。因为可能需要执行添加,所以不能对常量映射调用此函数:

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

请注意,如果发生了重新分配,对FruitMap.FindOrAdd(8)的调用可能会使此处的Ref7引用失效。

不要被名称迷惑,FindRef 函数会搜索键并返回值,而不是引用。如果找到了键,它会返回关联值的副本,否则返回默认构造的值类型。这会导致与FindOrAdd相似的行为,但因为FindRef函数返回的是值而不是引用,不会修改映射,所以可以对常量对象调用:

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 函数允许你执行反向查找(给定值,查找键)。因为值与键不同的是不会被散列,所以使用此函数时要注意,查找键是线性运算。而且,值不一定是唯一的,所以如果映射包含重复的值,针对特定值返回的键将是任意的对应键:

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

GenerateKeyArrayGenerateValueArray 分别使用所有键和所有值的副本填充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 函数可用于从映射移除元素并返回其值。名称中的“checked”表示该函数会检查键是否存在,如果不存在就断言它:

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 函数的作用与其相似,但会获取对要传递的值类型的引用,并返回布尔来表明是否找到了键。因此对缺失的键使用它不会产生运行时错误。如果没有找到键,调用会返回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 函数移除所有元素:

TMap<int32, FString> FruitMapCopy = FruitMap;
// FruitMapCopy == [
//  { Key:5, Value:"Mango"  },
//  { Key:4, Value:"Kiwi"   },
//  { Key:3, Value:"Orange" },
//  { Key:9, Value:"Melon"  }
// ]

FruitMapCopy.Empty();
// FruitMapCopy == []

和TArray一样,Empty会获取可选的松弛值,如果你要用给定数量的元素重新填充映射,它可用于优化。

排序

可以暂时对TMap排序。对映射到下一次迭代将会按排序后的顺序显示元素,但此后对映射的修改很可能会导致映射的顺序再次被打乱。排序是不稳定的,因此等价的元素可能以任何顺序出现。

你可以分别使用 KeySortValueSort 函数来按键或按值排序,这两个函数都使用二进制谓词指定排序顺序:

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 == []

松弛

Tmap还有一个 松弛 的概念,可以用来优化映射的填充。Reset 函数所起的作用类似于Empty()调用,但不会释放元素先前使用的内存:

FruitMap.Reset();
// FruitMap == [<invalid>, <invalid>, <invalid>]

在这里,和使用Empty一样晴空老影射,但是用于存储的内存并未被释放,而是作为松弛保留。

TMap并不提供像TArray::Max()一样检查预分配了多少元素的方法,但仍支持预分配松弛。Reserve函数可用于在添加前为特定数量的元素预分配松弛:

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

请注意,松弛恰好会导致新的元素按逆转顺序添加。这也是一个例证,表明无论如何都不能依靠映射中的元素顺序。

Shrink 函数的作用也和TArray的同名函数一样,会从容器的末尾移除所有被浪费的松弛。但是,因为TMap允许其数据结构中存在空位,所以它只会从结构末尾留下的空位移除松弛:

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调用只移除了一个无效元素,因为在末尾只有一个空位。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" }
// ]

KeyFuncs

只要类型有运算符==和非成员GetTypeHash重载,它就可以作为TMap的KeyType使用,不需要任何更改。但是,如果不希望重载这些函数,使用类型作为键也许很有用。在这些情况下,你可以提供你自己的自定义 KeyFuncs

KeyFuncs需要定义2个typedef和3个静态函数:

  • KeyInitType——用于传递键。

  • ElementInitType——用于传递元素。

  • KeyInitType GetSetKey(ElementInitType Element)——返回元素的键。

  • bool Matches(KeyInitType A, KeyInitType B)——返回A和B是否等价。

  • uint32 GetKeyHash(KeyInitType 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);
    }
};

这里有一个类型,它有唯一的辨识符作为其状态的一部分,但也有另一些状态对其身份没有影响。GetTypeHash和运算符==在这里不合适,因为要让运算符==忽略部分状态是不可能的,而且GetTypeHash需要匹配运算符==,但只要运算符==定义正确就做不到。但为了在映射中标识此类型,我们很乐意只使用状态的唯一辨识符部分。

  1. 首先,我们继承 BaseKeyFuncs,因为它可以帮助我们定义某些类型,包括KeyInitType和ElementInitType。我们将这些类型直接从Super提取到衍生类中,这样就可以在实现的其他部分中使用。

    BaseKeyFuncs使用两个模板参数:映射的元素类型和我们的键类型,即从GetSetKey返回的对象。和所有映射一样,元素类型是TPair,使用FMyStruct作为其KeyType,TMyStructMapKeyFuncs的模板参数作为其ValueType。我们将我们的替代KeyFuncs设置为模板,这样就可以按每个映射指定ValueType,而不必在每次要创建按FMyStruct设键的TMap时都定义新的KeyFuncs。第二个BaseKeyFuncs参数是键的类型,不要与TPair的“KeyType”混淆,后者是存储在元素的键字段中的。因为我们要使用FMyStruct::UniqueID作为我们的键,所以在此指定FString。

  2. 然后我们定义三个必需的KeyFuncs静态函数。第一个是GetSetKey,如果给定元素类型,它就会返回键。我们的元素类型是TPair,而我们的键是UniqueID,所以只要直接返回它就可以了。

    第二个静态函数是Matches,它获取两个元素的键(这两个键是已经使用GetSetKey从元素类型提取的),然后比较它们是否相等。FString的运算符==是区分大小写的,我们要进行区分大小写的搜索,所以我们使用FString::Compare函数和相应选项。

  3. 最后,GetKeyHash静态函数获取提取的键并返回它的散列值。GetTypeHash对于FString的行为又是忽略大小写的,所以我们调用区分大小写的FCrc函数来计算散列值。

  4. 现在我们可以使用新KeyFuncs来创建TMap了。我们还需要提供分配器,因为KeyFuncs参数是最后一个,但我们要替换掉默认值:

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

关于提供你自己的KeyFuncs的说明:要注意,TMap假定如果两个项使用KeyFuncs::Matches比较的结果是相等,那么它们会从KeyFuncs::GetKeyHash返回相同的值。此外,如果对现有映射元素的键进行的修改将会改变来自这两个函数中任一个的结果,那么系统会将这种修改视作未定义的行为,因为这会使TMap的内部散列失效。这些规则也适用于使用默认KeyFuncs时运算符==和GetKeyHash的重载。

其他

CountBytesGetAllocatedSize 函数用于估算内部数组当前使用了多少内存。CountBytes要获取FArchive,而GetAllocatedSize可以直接调用。它们通常用于状态报告。

Dump 函数要获取FOutputDevice,并写出一些关于映射内容的实现信息。它通常用于调试。

欢迎来到全新虚幻引擎4文档站!

我们正在努力开发新功能,包括反馈系统,以便您能对我们的工作作出评价。但它目前还未正式上线。如果您对此页面有任何意见与在使用中遭遇任何问题,请前往文档反馈论坛告知我们。

新系统上线运行后,我们会及时通知您的。

发表反馈意见