C++ pair(STL pair)類別範本的用法詳解

2020-07-16 10:04:32
我們已經知道 pair<const K, T> 物件是如何封裝鍵及其關聯的物件,也了解了 pair<const K, T> 物件是如何表示map容器中的元素的。一般來說,pair 物件可以封裝任意型別的物件,可以生成任何想生成的 pair<T1,T2> 物件,可以是陣列物件或者包含 pair<T1,T2> 的 vector 容器。例如,pair 可以封裝兩個序列容器或兩個序列容器的指標。pair<T1,T2> 模板定義在 utility 標頭檔案中,如果不想使用 map 而只想使用 pair 物件,可以包含這個標頭檔案。

pair 的操作

考慮到 pair 是一個比較簡單的模板型別,它只有兩個 public 資料成員 first 和 second。令人驚訝的是,它卻可以構造各種不同的 pair<T1,T2>。我們已經知道如何使用 first 和 second 來建立物件。

和右值參照引數一樣,pair 也有很多版本的參照引數,而且有一些版本的右值參照引數允許引數隱式轉換為所需的型別。例如,下面有 4 種不同的方式來建立一個 pair 物件:
std::string s1 {"test”}, s2{"that"};
std::pair<std::string, std::string> my_pair{s1, s2};
std::pair<std::string, std::string> your_pair{std::string {"test"},std::string {"that"}};
std::pair<std::string, std::string> his_pair{"test", std::string {"that"}};
std::pair<std::string, std::string> her_pair{"test", "that"};
第一個 pair 建構函式複製了所有引數的值,第二個移動引數值,第三個為了隱式轉換而將第一個引數傳給 string 的建構函式,最後一個建構函式將兩個引數隱式轉換為 string 物件而且它們會被移到 pair 的成員變數 first 和 second 中。由於這個建構函式有右值參照引數版本,因此任意一個或兩個模板型別引數可以是 unique_ptr<T>。

make_pair<T1,T2> 函數模板是一個輔助函數,可以生成並返回一個 pair<T1,T2> 物件。 可以如下所示生成先前程式碼塊中的 pair 物件:
auto my_pair = std::make_pair(s1, s2);
auto your_pair = std::make_pair(std::string {"test"},std::string {"that"});
auto his_pair = std::make_pair<std::string, std::string>("test",std::string {"that"});
auto her_pair = std::make_pair<std::string, std::string>("test", "that");
前兩條語句中的函數模板的型別引數由編譯器推斷。在最後兩條語句中,型別是明確的。如果在最後兩條語句中忽略模板型別引數,那麼物件型別將是 pair<const char*,string> 和 pair<const char*, const char*>。

pair 物件也可以複製或移動構造它的成員變數。例如:
std::pair<std::string, std:: string> new_pair{my_pair}; // Copy constructor
std::pair<std::string, std::string>
    old_pair{std::make_pair(std::string{"his"},std::string{"hers"})};
old_pair 是由 pair<string,string> 類的移動建構函式生成的。

另一個 pair 建構函式使用了 C++11 引入的這種機制,它允許通過在適當的位置生成 first 和 second 物件來構建 pair<T1, T2>。T1 和 T2 的建構函式的引數作為 tuple 引數傳給 pair 的建構函式。下一節會介紹如何使用 tuple 物件。下面是一個使用 pair 物件的範例:
std::pair<Name, Name> couple{std::piecewise_construct, std:: forward_as_tuple ("Jack","Jones") , std:: forward_as_tuple ("Jill", "Smith")};
這裡,pair 建構函式的第一個引數是一個定義在 utility 標頭檔案中的 piecewise_construct 型別的範例,這是一個用來作為標籤或標記的空型別。這個 piecewise_constmct 引數唯一的作用是區分這個建構函式的呼叫和有兩個 tuple 引數的建構函式呼叫之間的區別,後者的兩個引數通常用來作為 pair 成員變數 first 和 second 的值。

這裡,建構函式的第二和第三個引數指定了構造 first 和 second 物件的引數集,forward_as_tuple() 是一個定義在 tuple 標頭檔案中的函數模板。這裡用它的轉發引數生成了一個 tuple 參照。不會經常用到這種 pair 的建構函式,但它為不支援拷貝或移動運算的 T1 和 T2 型別提供了在適當位置生成 pair<T1,T2> 物件的獨特能力。

注意,如果引數是一個臨時物件,forward_as_tuple() 函數會生成一個右值參照的 tuple。 例如:
int a {1}, b {2};
const auto& c = std::forward_as_tuple(a,b);
這裡 c 的型別是 tuple<int&,int&>,因此成員變數是參照。但是假設這樣寫宣告的話:
const auto& c = std::forward_as_tuple(1,2);
這裡 c 的型別是 tuple<int &,int&>,成員變數作為值參照。
如果成員變數可以被複製和移動,pair 物件就支援複製和移動賦值。例如:
std::pair<std::string, std::string> old_pair; // Default constructor
std::pair<std::string, std::string> new_pair {std::string{"his"} , std::string{"hers"}};
old_pair = new_pair; // Copy assignment
new_pair = pair<std::string, std::string>
{std::string{"these"}, std::string{"those"}}; // Move assignment
預設的 pair 建構函式會用它的成員變數,即空的 string 物件來生成 old_pair 這是一個空的字串物件。第 3 條語句一個成員一個成員地將 new_pair 複製到 old_pair 中。第 4 條語句將作為賦值運算子的右運算元的 pair 物件的成員變數移到 new_pair 中。

當 pair 物件包含不同型別的成員變數時,也可以將一個 pair 物件賦值給另一個 pair 物件,只要作為右運算元的 pair 物件的成員變數可以隱式轉換為左運算元的 pair 物件的成員變數的型別。例如:
auto prl = std::make_pair ("these", "those"); // Type pair<const char*, const char*>
std::pair<std::string, std::string> pr2; // Type pair<string, string>
pr2 = prl; // OK in this case
prl 成員變數 first 和 second 的型別是 const char*。這個型別可以隱式轉換為 string,即 pr2 成員變數的型別,因此可以成功賦值。如果這些型別不能隱式轉換,這條賦值語句就無法通過編譯。

pair 物件有全套的運算子 ==、!=、<、<=、>、>=。這些運算子都可以正常使用,作為運算元的 pair 物件的型別必須是相同的,它們的成員變數的比較方式也必須相同。相等運算子返回 true,如果左右運算元的成員變數相等的話:
std::pair<std::string, std::string> new_pair;
new_pair.first = "his";
new_pair.second = "hers";
if (new_pair == std::pair<std::string, std::string> {"his", ,"hers"})
    std::cout << "Equality!n";
new_pair 的成員變數 first 和 second 被賦值為右運算元所包含的字串。如果 pair 物件是相等的,if 語句會輸出一些訊息。當兩個 pair 物件中的任何一個或兩個成員不相等時,!= 比較會返回 true。

對於小於或大於比較,pair 物件的成員變數是按字典順序比較的。如果 new_pair.first 小於 old_pair.first 的話,表示式 new_pair<old_pair 會返回 true。如果它們的成員變數 first 相等,但  new_pair.second 小於 old_pair.second,new_pair < old_pair 也為 true。下面是一個 範例:
std::pair<int, int> p1 {10, 9};
std::pair<int, int> p2 {10, 11};
std::pair<int, int> p3 {11, 9};
std::cout<<std::boolalpha << (p1 < p2) <<" "<<(pi > p3) << " "<< (p3 > p2) << std::endl;
第一個比較的結果為 true,因為 p1 和 p2 的成員變數 first 相等,p1 的成員變數 second 小於 p2 的成員變數 second。
第二個比較的結果為 false,因為 p1 的 first 小於 p3 的 first。 第三個比較的結果則為 true,因為 p3 的 first 大於 p2 的 first。

pair 的成員函數 swap() 可以和作為引數傳入的另一個 pair 物件交換其成員變數 first 和 second。顯然,引數必須是相同型別。下面有一個範例:
std::pair<int, int> p1 {10, 11};
std::pair<int, int> p2 {11, 9};
p1.swap(p2); // p1={ll,9} p2={10/11}
如果執行兩次 swap(),物件恢復成原來的值。