作爲關聯式容器的一種,map 容器儲存的都是 pair 物件,也就是用 pair 類別範本建立的鍵值對。其中,各個鍵值對的鍵和值可以是任意數據型別,包括 C++ 基本數據型別(int、double 等)、使用結構體或類自定義的型別。
與此同時,在使用 map 容器儲存多個鍵值對時,該容器會自動根據各鍵值對的鍵的大小,按照既定的規則進行排序。預設情況下,map 容器選用std::less排序規則(其中 T 表示鍵的數據型別),其會根據鍵的大小對所有鍵值對做升序排序。當然,根據實際情況的需要,我們可以手動指定 map 容器的排序規則,既可以選用 STL 標準庫中提供的其它排序規則(比如std::greater),也可以自定義排序規則。
另外需要注意的是,使用 map 容器儲存的各個鍵值對,鍵的值既不能重複也不能被修改。換句話說,map 容器中儲存的各個鍵值對不僅鍵的值獨一無二,鍵的型別也會用 const 修飾,這意味着只要鍵值對被儲存到 map 容器中,其鍵的值將不能再做任何修改。
map 容器定義在 < map > 標頭檔案中,並位於 std 名稱空間中。
通過呼叫 map 容器類的預設建構函式,可以建立出一個空的 map 容器,比如:
std::map<std::string, int> myMap;
通過此方式建立出的 myMap 容器,初始狀態下是空的,即沒有儲存任何鍵值對。鑑於空 map 容器可以根據需要隨時新增新的鍵值對,因此建立空 map 容器是比較常用的。
當然在建立 map 容器的同時,也可以進行初始化,比如:
std::map<std::string, int> myMap{ {"C語言",10},{"STL",20} };
由此,myMap 容器在初始狀態下,就包含有 2 個鍵值對。
再次強調,map 容器中儲存的鍵值對,其本質都是 pair 類別範本建立的 pair 物件。因此,下面 下麪程式也可以建立出一模一樣的 myMap 容器:
std::map<std::string, int> myMap{std::make_pair("C語言",10),std::make_pair("STL",20)};
除此之外,在某些場景中,可以利用先前已建立好的 map 容器,再建立一個新的 map 容器。例如:
std::map<std::string, int> newMap(myMap);
由此,通過呼叫 map 容器的拷貝(複製)建構函式,即可成功建立一個和 myMap 完全一樣的 newMap 容器。
C++ 11 標準中,還爲 map 容器增添了移動建構函式。當有臨時的 map 物件作爲參數,傳遞給要初始化的 map 容器時,此時就會呼叫移動建構函式。舉個例子:
#建立一個會返回臨時 map 物件的函數
std::map<std::string,int> disMap() {
std::map<std::string, int> tempMap{ {"C語言",10},{"STL",20} };
return tempMap;
}
//呼叫 map 類別範本的移動建構函式建立 newMap 容器
std::map<std::string, int> newMap(disMap());
map 類別範本還支援取已建 map 容器中指定區域內的鍵值對,建立並初始化新的 map 容器。例如:
std::map<std::string, int> myMap{ {"C語言",10},{"STL",20} };
std::map<std::string, int> newMap(++myMap.begin(), myMap.end());
這裏,通過呼叫 map 容器的雙向迭代器,實現了在建立 newMap 容器的同時,將其初始化爲包含一個 {「STL」,20} 鍵值對的容器。
當然,在以上幾種建立 map 容器的基礎上,我們都可以手動修改 map 容器的排序規則。預設情況下,map 容器呼叫 std::less 規則,根據容器內各鍵值對的鍵的大小,對所有鍵值對做升序排序。
因此,如下 2 行建立 map 容器的方式,其實是等價的:
std::map<std::string, int> myMap{ {"C語言",10},{"STL",20} };
std::map<std::string, int, std::less<std::string>> myMap{ {"C語言",10},{"STL",20} };
以上兩種建立方式生成的 myMap 容器,其內部鍵值對排列的順序爲:
<"C語言", 10>
<"STL", 20>
下面 下麪程式手動修改了 myMap 容器的排序規則,令其作升序排序:
std::map<std::string, int, std::greater<std::string>> myMap{ {"C語言",10},{"STL",20} };
此時,myMap 容器內部鍵值對排列的順序爲:
<"STL", 20>
<"C語言", 10>
下面 下麪,上一段程式碼:
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{
//建立空 map 容器,預設根據個鍵值對中鍵的值,對鍵值對做降序排序
std::map<std::string, std::string, std::greater<std::string>> myMap;
//呼叫 emplace() 方法,直接向 myMap 容器中指定位置構造新鍵值對
myMap.emplace("C語言","111");
myMap.emplace("Python", "222");
myMap.emplace("STL", "333");
//輸出當前 myMap 容器儲存鍵值對的個數
std::cout << "myMap size == " << myMap.size() << std::endl;
//判斷當前 myMap 容器是否爲空
if (!myMap.empty())
{
//藉助 myMap 容器迭代器,將該容器的鍵值對逐個輸出
for (auto i = myMap.begin(); i != myMap.end(); ++i)
{
std::cout << i->first << " " << i->second << std::endl;
}
}
return 0;
}
輸出結果爲:
myMap size == 3
STL 333
Python 222
C語言 111
C++ STL 標準庫爲 map 容器配備的是雙向迭代器(bidirectional iterator)。這意味着,map 容器迭代器只能進行 ++p、p++、–p、p–、*p 操作,並且迭代器之間只能使用 == 或者 != 運算子進行比較。
下面 下麪程式以 begin()/end() 組合爲例,演示瞭如何遍歷 map 容器:
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{
//建立並初始化 map 容器
std::map<std::string, std::string>myMap{ {"STL","111"},{"C++","222"} };
//呼叫 begin()/end() 組合,遍歷 map 容器
for (auto iter = myMap.begin(); iter != myMap.end(); ++iter)
{
cout << iter->first << " " << iter->second << endl;
}
return 0;
}
輸出結果爲:
C++ 222
STL 111
除此之外,map 類別範本中還提供了 find() 成員方法,它能幫我們查詢指定 key 值的鍵值對,如果成功找到,則返回一個指向該鍵值對的雙向迭代器;反之,其功能和 end() 方法相同。
舉個例子:
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{
//建立並初始化 map 容器
std::map<std::string, std::string> myMap{ {"STL","111"},
{"C++","222"},
{"Java","333"} };
//查詢鍵爲 "Java" 的鍵值對
auto iter = myMap.find("Java");
//從 iter 開始,遍歷 map 容器
for (; iter != myMap.end(); ++iter)
{
cout << iter->first << " " << iter->second << endl;
}
return 0;
}
輸出結果:
Java 333
STL 111
同時,map 類別範本中還提供有 lower_bound(key) 和 upper_bound(key) 成員方法,它們的功能是類似的,唯一的區別在於:
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{
//建立並初始化 map 容器
std::map<std::string, std::string>myMap{ {"STL","111"},
{"C++","222"},
{"Java","333"} };
//找到第一個鍵的值不小於 "Java" 的鍵值對
auto iter = myMap.lower_bound("Java");
cout << "lower:" << iter->first << " " << iter->second << endl;
//找到第一個鍵的值大於 "Java" 的鍵值對
iter = myMap.upper_bound("Java");
cout <<"upper:" << iter->first << " " << iter->second << endl;
return 0;
}
輸出結果:
lower:Java 333
upper:STL 111
equal_range(key) 成員方法可以看做是 lower_bound(key) 和 upper_bound(key) 的結合體,該方法會返回一個 pair 物件,其中的 2 個元素都是迭代器型別,其中 pair.first 實際上就是 lower_bound(key) 的返回值,而 pair.second 則等同於 upper_bound(key) 的返回值。
顯然,equal_range(key) 成員方法表示的一個範圍,位於此範圍中的鍵值對,其鍵的值都爲 key。舉個例子:
#include <iostream>
#include <utility>
#include <map>
#include <string>
using namespace std;
int main()
{
//建立並初始化 map 容器
std::map<string, string> myMap{ {"STL","111"},
{"C++","222"},
{"Java","333"} };
//建立一個 pair 物件,來接收 equal_range() 的返回值
std::pair<std::map<string, string>::iterator, std::map<string, string>::iterator> myPair = myMap.equal_range("C++");
//通過遍歷,輸出 myPair 指定範圍內的鍵值對
for (auto iter = myPair.first; iter != myPair.second; ++iter)
{
cout << iter->first << " " << iter->second << endl;
}
return 0;
}
輸出結果:
C++ 222
和 lower_bound(key)、upper_bound(key) 一樣,該方法也更常用於 multimap 容器,因爲 map 容器中各鍵值對的鍵的值都是唯一的,因此通過 map 容器呼叫此方法,其返回的範圍內最多也只有 1 個鍵值對。
map 類別範本中對[ ]運算子進行了過載,這意味着,類似於藉助陣列下標可以直接存取陣列中元素,通過指定的鍵,我們可以輕鬆獲取 map 容器中該鍵對應的值。
舉個例子:
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{
//建立並初始化 map 容器
std::map<std::string, std::string>myMap{ {"STL","111"},
{"C++","222"},
{"Java","333"} };
string cValue = myMap["C++"];
cout << cValue << endl;
return 0;
}
輸出結果:
222
注意,只有當 map 容器中確實存有包含該指定鍵的鍵值對,藉助過載的 [ ] 運算子才能 纔能成功獲取該鍵對應的值;反之,若當前 map 容器中沒有包含該指定鍵的鍵值對,則此時使用 [ ] 運算子將不再是存取容器中的元素,而變成了向該 map 容器中增添一個鍵值對。其中,該鍵值對的鍵用 [ ] 運算子中指定的鍵,其對應的值取決於 map 容器規定鍵值對中值的數據型別,如果是基本數據型別,則值爲 0;如果是 string 型別,其值爲 " ",即空字串(即使用該型別的預設值作爲鍵值對的值)。
舉個例子:
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{
//建立空 map 容器
std::map<std::string, int> myMap;
int cValue = myMap["C++"];
for (auto i = myMap.begin(); i != myMap.end(); ++i)
{
cout << i->first << " "<< i->second << endl;
}
return 0;
}
輸出結果:
C++ 0
顯然,對於空的 myMap 容器來說,其內部沒有以 「C++」 爲鍵的鍵值對,這種情況下如果使用 [ ] 運算子獲取該鍵對應的值,其功能就轉變成了向該 myMap 容器中新增一個<「C++」,0>鍵值對(由於 myMap 容器規定各個鍵值對的值的型別爲 int,該型別的預設值爲 0)。
實際上,[ ] 運算子確實有 "爲 map 容器新增新鍵值對」 的功能,但前提是要保證新新增鍵值對的鍵和當前 map 容器中已儲存的鍵值對的鍵都不一樣。例如:
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{
//建立空 map 容器
std::map<string, string>myMap;
myMap["STL"] = "111";
cout << "STL " << myMap["STL"] << endl;
myMap["Python"] = "222";
myMap["STL"] = "333";
for (auto i = myMap.begin(); i != myMap.end(); ++i)
{
cout << i->first << " " << i->second << endl;
}
return 0;
}
輸出結果:
STL 111
Python 222
STL 333
除了藉助 [ ] 運算子獲取 map 容器中指定鍵對應的值,還可以使用 at() 成員方法。和前一種方法相比,at() 成員方法也需要根據指定的鍵,才能 纔能從容器中找到該鍵對應的值;不同之處在於,如果在當前容器中查詢失敗,該方法不會向容器中新增新的鍵值對,而是直接拋出 out_of_range 異常。
舉個例子:
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{
//建立並初始化 map 容器
std::map<std::string, std::string> myMap{ {"STL","111"},
{"C++","222"},
{"Java","333"} };
cout << myMap.at("C++") << endl;
//下面 下麪一行程式碼會引發 out_of_range 異常
//cout << myMap.at("Python") << endl;
return 0;
}
輸出結果:
222
除了可以直接獲取指定鍵對應的值之外,還可以藉助 find() 成員方法間接實現此目的。和以上 2 種方式不同的是,該方法返回的是一個迭代器,即如果查詢成功,該迭代器指向查詢到的鍵值對;反之,則指向 map 容器最後一個鍵值對之後的位置(和 end() 成功方法返回的迭代器一樣)。
舉個例子:
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{
//建立並初始化 map 容器
std::map<std::string, std::string> myMap{ {"STL","111"},
{"C++","222"},
{"Java","333"} };
map< std::string, std::string >::iterator myIter = myMap.find("C++");
cout << myIter->first << " " << myIter->second << endl;
return 0;
}
輸出結果:
C++ 222
如果以上方法都不適用,我們還可以遍歷整個 map 容器,找到包含指定鍵的鍵值對,進而獲取該鍵對應的值。比如:
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{
//建立並初始化 map 容器
std::map<std::string, std::string> myMap{ {"STL","111"},
{"C++","222"},
{"Java","333"} };
for (auto iter = myMap.begin(); iter != myMap.end(); ++iter)
{
//呼叫 string 類的 compare() 方法,找到一個鍵和指定字串相同的鍵值對
if (!iter->first.compare("C++"))
{
cout << iter->first << " " << iter->second << endl;
}
}
return 0;
}
輸出結果:
C++ 222
需指定插入位置,直接將鍵值對新增到 map 容器中。insert() 方法的語法格式有以下 2 種:
//1、參照傳遞一個鍵值對
pair<iterator,bool> insert (const value_type& val);
//2、以右值參照的方式傳遞鍵值對
template <class P>
pair<iterator,bool> insert (P&& val);
其中,val 參數表示鍵值對變數,同時該方法會返回一個 pair 物件,其中 pair.first 表示一個迭代器,pair.second 爲一個 bool 型別變數:
舉個例子:
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{
//建立一個空 map 容器
std::map<string, string> mymap;
//建立一個真實存在的鍵值對變數
std::pair<string, string> STL = { "STL","111" };
//建立一個接收 insert() 方法返回值的 pair 物件
std::pair<std::map<string, string>::iterator, bool> ret;
//插入 STL,由於 STL 並不是臨時變數,因此會以第一種方式傳參
ret = mymap.insert(STL);
cout << "ret.iter = <{" << ret.first->first << ", " << ret.first->second << "}, " << ret.second << ">" << endl;
//以右值參照的方式傳遞臨時的鍵值對變數
ret = mymap.insert({ "C++","222" });
cout << "ret.iter = <{" << ret.first->first << ", " << ret.first->second << "}, " << ret.second << ">" << endl;
//插入失敗樣例
ret = mymap.insert({ "STL","333" });
cout << "ret.iter = <{" << ret.first->first << ", " << ret.first->second << "}, " << ret.second << ">" << endl;
return 0;
}
輸出結果:
ret.iter = <{STL, 111}, 1>
ret.iter = <{C++, 222}, 1>
ret.iter = <{STL, 111}, 0>
從執行結果中不難看出,程式中共執行了 3 次插入操作,其中成功了 2 次,失敗了 1 次:
另外,還可以使用如下 2 種方式建立臨時的鍵值對變數,它們是等價的:
//呼叫 pair 類別範本的建構函式
ret = mymap.insert(pair<string,string>{ "C++","222" });
//呼叫 make_pair() 函數
ret = mymap.insert(make_pair("C++", "222"));
除此之外,insert() 方法還支援向 map 容器的指定位置插入新鍵值對,該方法的語法格式如下:
//以普通參照的方式傳遞 val 參數
iterator insert (const_iterator position, const value_type& val);
//以右值參照的方式傳遞 val 鍵值對參數
template <class P>
iterator insert (const_iterator position, P&& val);
其中 val 爲要插入的鍵值對變數。注意,和第 1 種方式的語法格式不同,這裏 insert() 方法返回的是迭代器,而不再是 pair 物件:
舉個例子:
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{
//建立一個空 map 容器
std::map<string, string> mymap;
//建立一個真實存在的鍵值對變數
std::pair<string, string> STL = { "STL","111" };
//指定要插入的位置
std::map<string, string>::iterator it = mymap.begin();
//向 it 位置以普通參照的方式插入 STL
auto iter1 = mymap.insert(it, STL);
cout << iter1->first << " " << iter1->second << endl;
//向 it 位置以右值參照的方式插入臨時鍵值對
auto iter2 = mymap.insert(it, std::pair<string, string>("C++", "222"));
cout << iter2->first << " " << iter2->second << endl;
//插入失敗樣例
auto iter3 = mymap.insert(it, std::pair<string, string>("STL", "333"));
cout << iter3->first << " " << iter3->second << endl;
return 0;
}
輸出結果:
STL 111
C++ 222
STL 111
insert() 方法還支援向當前 map 容器中插入其它 map 容器指定區域內的所有鍵值對,該方法的語法格式如下:
template <class InputIterator>
void insert (InputIterator first, InputIterator last);
其中 first 和 last 都是迭代器,它們的組合<first,last>可以表示某 map 容器中的指定區域。
舉個例子:
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{
//建立並初始化 map 容器
std::map<std::string, std::string> mymap{ {"STL","111"},
{"C++","222"},
{"Java","333"} };
//建立一個空 map 容器
std::map<std::string, std::string> copymap;
//指定插入區域
std::map<string, string>::iterator first = ++mymap.begin();
std::map<string, string>::iterator last = mymap.end();
//將<first,last>區域內的鍵值對插入到 copymap 中
copymap.insert(first, last);
//遍歷輸出 copymap 容器中的鍵值對
for (auto iter = copymap.begin(); iter != copymap.end(); ++iter)
{
cout << iter->first << " " << iter->second << endl;
}
return 0;
}
輸出結果:
Java 333
STL 111
此程式中,<first,last> 指定的區域是從 mymap 容器第 2 個鍵值對開始,之後所有的鍵值對,所以 copymap 容器中包含有 2 個鍵值對。
除了以上一種格式外,insert() 方法還允許一次向 map 容器中插入多個鍵值對,其語法格式爲:
void insert ({val1, val2, ...});
其中,vali 都表示的是鍵值對變數。
舉個例子:
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{
//建立空的 map 容器
std::map<std::string, std::string> mymap;
//向 mymap 容器中新增 3 個鍵值對
mymap.insert({ {"STL", "111"},
{"C++","222"},
{"Java","333"} });
for (auto iter = mymap.begin(); iter != mymap.end(); ++iter)
{
cout << iter->first << " " << iter->second << endl;
}
return 0;
}
輸出結果:
C++ 222
Java 333
STL 111
和 insert() 方法相比,emplace() 和 emplace_hint() 方法的使用要簡單很多,因爲它們各自只有一種語法格式。其中,emplace() 方法的語法格式如下:
template <class... Args>
pair<iterator,bool> emplace (Args&&... args);
參數 (Args&&… args) 指的是,這裏只需要將建立新鍵值對所需的數據作爲參數直接傳入即可,此方法可以自行利用這些數據構建出指定的鍵值對。
另外,該方法的返回值也是一個 pair 物件,其中 pair.first 爲一個迭代器,pair.second 爲一個 bool 型別變數:
下面 下麪程式演示 emplace() 方法的具體用法:
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{
//建立並初始化 map 容器
std::map<string, string>mymap;
//插入鍵值對
pair<map<string, string>::iterator, bool> ret = mymap.emplace("STL", "111");
cout << "1、ret.iter = <{" << ret.first->first << ", " << ret.first->second << "}, " << ret.second << ">" << endl;
//插入新鍵值對
ret = mymap.emplace("C++", "222");
cout << "2、ret.iter = <{" << ret.first->first << ", " << ret.first->second << "}, " << ret.second << ">" << endl;
//失敗插入的樣例
ret = mymap.emplace("STL", "333");
cout << "3、ret.iter = <{" << ret.first->first << ", " << ret.first->second << "}, " << ret.second << ">" << endl;
return 0;
}
輸出結果:
1、ret.iter = <{STL, 111}, 1>
2、ret.iter = <{C++, 222}, 1>
3、ret.iter = <{STL, 111}, 0>
可以看到,程式中共執行了 3 次向 map 容器插入鍵值對的操作,其中前 2 次都成功了,第 3 次由於要插入的鍵值對的鍵和 map 容器中已存在的鍵值對的鍵相同,因此插入失敗。
emplace_hint() 方法的功能和 emplace() 類似,其語法格式如下:
template <class... Args>
iterator emplace_hint (const_iterator position, Args&&... args);
顯然和 emplace() 語法格式相比,有以下 2 點不同:
下面 下麪程式演示 emplace_hint() 方法的用法:
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main()
{
//建立並初始化 map 容器
std::map<string, string>mymap;
//指定在 map 容器插入鍵值對
map<string, string>::iterator iter = mymap.emplace_hint(mymap.begin(),"STL", "111");
cout << iter->first << " " << iter->second << endl;
iter = mymap.emplace_hint(mymap.begin(), "C++", "222");
cout << iter->first << " " << iter->second << endl;
//插入失敗樣例
iter = mymap.emplace_hint(mymap.begin(), "STL", "333");
cout << iter->first << " " << iter->second << endl;
return 0;
}
輸出結果:
STL 111
C++ 222
STL 111
注意,和 insert() 方法一樣,雖然 emplace_hint() 方法指定了插入鍵值對的位置,但 map 容器爲了保持儲存鍵值對的有序狀態,可能會移動其位置。