Key-Value Hash
enum class EPieceType { King, Queen, Rook, Bishop, Knight, Pawn }; struct FPiece { int32 PlayerId; EPieceType Type; FIntPoint Position; FPiece(int32 InPlayerId, EPieceType InType, FIntVector InPosition) : PlayerId(InPlayerId), Type(InType), Position(InPosition) { } }; class FBoard { private: // Using a TMap, we can refer to each piece by its position TMap<FIntPoint, FPiece> Data; public: bool HasPieceAtPosition(FIntPoint Position) { return Data.Contains(Position); } FPiece GetPieceAtPosition(FIntPoint Position) { return Data[Position]; } void AddNewPiece(int32 PlayerId, EPieceType Type, FIntPoint Position) { FPiece NewPiece(PlayerId, Type, Position); Data.Add(Position, NewPiece); } void MovePiece(FIntPoint OldPosition, FIntPoint NewPosition) { FPiece Piece = Data[OldPosition]; Piece.Position = NewPosition; Data.Remove(OldPosition); Data.Add(NewPosition, Piece); } void RemovePieceAtPosition(FIntPoint Position) { Data.Remove(Position); } void ClearBoard() { Data.Empty(); } };
創建
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" } // ]
add同key元素會覆蓋
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:"" } // ]
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" } // ]
使用 Append 函數進行合並即可插入來自另一個映射的所有元素:同key覆蓋
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) { 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:"" } // ]
注意:如已發生重新分配,此處的 Ref7 引用可能已被 FruitMap.FindOrAdd(8) 的調用無效化。
無視 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
GenerateKeyArray 和 GenerateValueArray 函數分別允許以全部鍵和值的副本對陣列進行填入。在兩種情況下,被傳遞的陣列在填入前會被清空,因此元素的生成數量將始終等於映射中的元素數量。
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 == []
排序
可對 TMap 進行臨時排序。映射上的下次迭代將以排序順序展示元素,之后對映射進行的修改可能導致映射重新排序。排序並不穩定,因此相等元素可能以各種排列方式出現。
使用 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
TMap 也擁有 slack 的概念,可用於優化映射的填入。Reset 與 Empty() 調用作用相似,但不會釋放元素之前使用的內存。
FruitMap.Reset(); // FruitMap == [<invalid>, <invalid>, <invalid>]
此處映射按照 Empty 相同的方式進行清空,但用於儲存的內存不會被釋放,仍為 slack。
TMap 不會像 TArray::Max() 一樣提供檢查預分配元素的數量,但仍然支持預分配 slack。Reserve 函數可用於在添加之前預分配特定數量元素的 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" } // ]
注意:Slack 會導致新元素以倒序被添加。這是為什么不可信賴映射中元素排序的原因。
Shrink 函數和 TArray 中相等函數的相同之處是 - 它將從容器末端移除被廢棄的 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 調用移除,因為末端只有一個洞。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 個 typedefs 和 3 個靜態函數的定義:
-
KeyInitType - 用於傳遞鍵
-
ElementInitType - 用於傳遞元素
-
KeyInitType GetSetKey(ElementInitType Element) - 返回元素的鍵。
-
bool Matches(KeyInitType A, KeyInitType B) - 返回 A 和 B 是否相等。
-
uint32 GetKeyHash(KeyInitType Key) - 返回鍵的散列值。
KeyInitType 和 ElementInitType 是鍵類型和元素類型普通傳遞慣例的 typedefs。它們通常為淺顯類型的一個值和非淺顯類型的一個常量引用。需牢記:映射的元素類型為 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 需要和運算符 == 相匹配;如運算符 == 已被正確定義,則無法進行。然而,出於在映射中識別此類型的目的,我們可只使用狀態的 UniqueID。
最后,我們將繼承 BaseKeyFuncs,因為它有助於為我們進行一些內容的定義,包括 KeyInitType 和 ElementInitType。直接將它們從 Super 拉到派生類中,以便在實現的剩余部分中使用。
BaseKeyFuncs 接受兩個模板參數:映射的元素類和鍵的類型(從 GetSetKey 返回的對象)。和所有的映射一樣,元素類型是 TPair,接受 FMyStruct 作為其 KeyType、接受 TMyStructMapKeyFuncs 的模板參數作為其 ValueType。我們使替代 KeyFuncs 成為模板,使 ValueType 以每個函數為基礎進行指定;每次創建 FMyStruct 上有鍵的 TMap 時,均無需對新的 KeyFuncs 進行定義。第二個 BaseKeyFuncs 參數是鍵類型,不要將其與 TPair 的“KeyType”混淆。此 KeyType 存儲在元素的鍵區中。需要將 FMyStruct::UniqueID 作為鍵使用,因此我們需要在此處指定 FString。
然后對所需要的 3 個 KeyFuncs 靜態函數進行指定。第一個是 GetSetKey,被給定一個元素類型,將返回鍵。我們的元素類型是 TPair,key 是 UniqueID,因此我們將直接返回。
第二個靜態函數是 Matches,它接受兩個元素的鍵(已使用 GetSetKey 從元素類型中提取),然后將它們進行比較,確定是否為相等。FString 的運算符 == 不區分大小寫,但我們需要執行區分大小寫的搜索,因此應結合適當的選項使用 FString::Compare 函數。
最后,GetKeyHash 靜態函數接受提取鍵,並返回一個它的散列值。再次提醒,FString 的 GetTypeHash 行為會忽略大小寫,因此我們調用一個區分大小寫的 FCrc 函數進行散列的計算。
現在我們可使用新 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 的重載。
雜項
CountBytes 和 GetAllocatedSize 函數用於估計陣列當前應用的內存量。CountBytes 接受 FArchive,GetAllocatedSize 可被直接調用。它們常用於統計報告。
Dump 函數接受 FOutputDevice 並寫出關於映射內容的部分實現信息。它通常用於調試。