UE4 TMap


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 並寫出關於映射內容的部分實現信息。它通常用於調試。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM