C++ unordered_map初始化詳解

2020-07-16 10:04:30
生成 unordered_map 容器和生成 map 一樣簡單,只要可以用 hash<K> 的範例雜湊 k 型別的鍵,而且必須能夠用 == 運算子來比較鍵。下面展示了如何定義和初始化 unordered_map:
std::unordered_map<std::string, size_t> people {{"Jan",44}, {"Jim", 33}, {"Joe", 99}}; // Name,age
這樣就生成了一個包含 pair<string,size_t> 元素的容器,並用初始化列表中的元素對它進行了初始化。容器中格子的個數是預設的,它使用 equal_to<String>() 物件來判斷鍵是否相等。它會用定義在 string 標頭檔案中的 hash<string> 來對 string 進行雜湊。如果沒有提供初始值,預設的建構函式會生成一個空容器,它有預設個數的格子。

當我們知道要在容器中儲存多少個元素時,可以在建構函式中指定應該分配的格子的個數:
std::unordered_map<std::string,size_t> people {{ { "Jan", 44}, {"Jim", 33}, {"Joe", 99}}, 10};
這個建構函式有兩個引數:初始化列表和需要分配的格子數。

也可以用疊代器定義的一段 pair 物件來生成容器。顯然,只要這個範圍內的 pair 物件都是要求的型別,那麼任何物件源都可以接受。例如:
std::vector<std::pair<string, size_t>>folks {{ "Jan",44}, {"Jim", 33}, {"Joe", 99},{"Dan", 22},{"Ann", 55}, {"Don", 77}};
std::unordered_map<string, size_t> neighbors {std::begin(folks), std::end(folks) , 500};
folks 是一個包含 pair<string,size_t> 型別元素的 vector 容器,然後用它的元素來填充 neighbors 容器。這裡為 neighbors 分配了 500 個格子,但也可以省略這個引數,使用預設的格子個數。可以為前面的兩個建構函式指定定義雜湊函數的函數物件。這個函數物件會分別作為第 1 個建構函式的第 3 個引數,以及第 2 個建構函式的第 4 個引數,所以這時需要為第 2 個建構函式指定格子個數。接下來會展示如何在接收初始化列表的建構函式中指定這個引數。

假如我們想要用定義在前面章節中的 Name 物件作為鍵,那就必須為它定義一個雜湊函數和一個恆等運算子,擴充套件後的類的定義如下:
class Name
{
public:
    size_t hash() const { return std::hash<std::string>()(first+second); }
    bool operator==(const Name& name) const { return first == name.first && second== name.second; }
};
在這個範例中,編譯器提供的預設的 operator==() 成員函數能夠滿足我們的要求,但還是想自己定義。成員函數 hash() 用函數物件 hash<string>() 來雜湊 Name 物件的成員 first 和 second 所拼接的字串。

unordered_map 容器的雜湊函數只能接受和鍵同型別的單個引數,它會返回一個 size_t 型別的雜湊值。我們可以定義一個滿足這些條件的函數物件的型別,這個型別的函數物件會呼叫 Name 物件的成員函數 hash():
class Hash_Name {
public:
    size_t operator()(const Name& name) const { return name.hash(); }
};
當生成 unordered_map 容器時,可以用 Hash_Name 物件作為它的比較函數:
std::unordered_map<Name, size_t, Hash_Name> people
{{{{"Ann", "Ounce"}, 25}, {{"Bill", "Bao"}, 46}, {{"Jack", "Sprat"}, 77}},500,Hash Name()}
這個容器中的元素是 pair<Name, size_t> 型別物件。它的建構函式的第一個引數是一個初始化列表,裡面定義了三個這種型別的物件。注意括號是如何巢狀的。最內層的括號中包含 Name 建構函式的引數。它上面的一層包含的是 pair<Name,size_t> 建構函式的引數。

unordered_map 建構函式的第 2 個引數是格子的個數,我們必須指定它,因為我們想使用第 3 個引數,第 3 個引數是用來雜湊鍵的函數物件。Hash_Name 型別的函數物件會作為這個容器的第 3 個模板型別引數。這麼做是必要的,因為模板型別引數有一個不同於我們函數物件的型別的預設值。unordered_map 有以元素段為引數的建構函式,它的前兩個引數是疊代器,第 3 個引數是格子個數,第 4 個引數是雜湊函數。

當需要指定用來比較兩個鍵物件是否相等的函數物件時,必須指定格子個數,函數物件會用鍵值來生成雜湊值。如果我們忽略了 Name 類的成員函數 operator==(),並且假設定義了一個定義了函數物件的類型別 Name_Equal,可以按如下方式在建構函式中指定它:
std::unordered_map<Name, size_t, Hash_Name, Name_Equal〉 people
{ { { {"Ann", "Ounce"}, 25}, {{"Bill”, "Bao"}, 46},{{"Jack","Sprat"}, 77}},500,Hash_Name(), Name_Equal()};
這裡有一個額外的模板型別引數和一個額外的建構函式引數,因為引數有預設值,所以這個模板型別引數是必要的。模板參數列中用來比較鍵的函數物件同樣會用在以初始化列表為引數的建構函式中。

unordered_map 也有移動和拷貝建構函式。顯然,可以用它們生成容器的副本,副本容器的格子個數、雜湊函數都和引數容器相同。