C++ 11 特性:關聯容器map、set的使用
參考文獻《C++ Primer》
一、關聯容器概述
1.1 關聯容器的概念
關聯容器支援高效的查詢與訪問,主要的關聯容器為map與set這兩個。其中map主要提供的是鍵-值的操作,比如字典;set主要提供的是集合的操作,比如去重。標準庫總計提供了8個關聯容器。
按關鍵字有序儲存元素
名稱 | 說明 |
---|---|
map | 關聯陣列,儲存<關鍵字,鍵>對 |
set | 只儲存關鍵字的容器 |
multimap | 關鍵字可以重複出現的map |
multiset | 關鍵字可以重複出現的set |
無序集合
名稱 | 說明 |
---|---|
unordered_map | 使用雜湊函式組織的map |
unordered_set | 使用雜湊函式組織的set |
unordered_multimap | 使用雜湊函式組織的map,關鍵字可以重複出現 |
unordered_multiset | 使用雜湊函式組織的set,關鍵字可以重複出現 |
8中關聯容器中,map與multimap定義在標頭檔案<map>
中;set與multiset定義在標頭檔案<set>
中;無序容器則分別定義在標頭檔案<unordered_map>
與<unordered_set>
1.2 pair型別
在介紹關聯容器的操作前,需要掌握pair型別的操作,它定義在標頭檔案<utility>
中。
pair通常用來生成一個特定型別的模板,儲存兩個資料成員。例如
pair <string, string> name; //儲存兩個string
pair <string, size_t> word_count; //儲存一個string和一個size_t
pair<string, vector<int>> line; //儲存string和vector<int>
1.3 pair型別的操作
名稱 | 說明 |
---|---|
pair < T1 , T2 > p | p中第一個成員的型別為T1,第二個成員的型別為T2 |
pair < T1 , T2 > p(v1,v2) | p中第一個成員初始化為v1,第二個成員初始化為v2 |
pair < T1 , T2 > p = {v1,v2} | 等價p(v1,v2) |
make_pair(v1,v2) | 返回一個用v1,v2初始化的pair型別 |
p.first | 返回p的第一個資料成員 |
p.second | 返回p的第二個資料成員 |
p1 == p2 | 當第一個與第二個成員分別相等時,兩個pair相等 |
二、關聯容器操作
2.1 關聯容器的定義
關聯容器set與map的定義如下
map<string, size_t> word_count; //空容器
//每個<鍵,值>對在一個花括號中
map<string, string> name = { {"Joce","James"},{"Aust","Jane"}};
set<string> exclude = { "the","a","but","or" };
2.2 關聯容器的基本操作
2.2.1 關聯容器的相關型別
除了容器的基本型別外,關聯容器還具備如下型別。
名稱 | 說明 |
---|---|
key_type | 此容器型別的關鍵字型別 |
mapped_type | 僅適用於map,關鍵字對應的值的型別 |
value_type | 對於set與key_type相同 對於map為 pair<const key_type,mapped_type> 型別 |
2.2.1 關聯容器與迭代器
map
map<string, string> word_count{ {"1.1","1.2"},{"2.1","2.2"} };
//迭代器指向map的首元素,這裡的map_iter指標指向的是pair<const string,string>型別
auto map_iter = word_count.begin();
//首元素的第一個成員,執行結果為1.1
cout << map_iter->first << endl;
//第二個元素的第二個成員,執行結果為2.2
cout << (++map_iter)->second << endl;
//錯誤,因為是pair<const string,string>型別,所以無法通過指標修改關鍵字
map_iter->first = "修改1.1";
//正確,但是可以通過指標修改值。
map_iter->second = "修改2.2";
//執行結果為:
//2.1
//修改2.2
cout << map_iter->first << endl;
cout << map_iter->second << endl;
map的value_type是pair<const T1,T2>
型別的,我們可以修改pair的值,但是不能修改其關鍵字。
set
//定義一個集合
set<int> s = { 1,2,3,4,5 };
//迭代器set_iter指向集合首元素
set<int>::iterator set_iter = s.begin();
while (set_iter != s.end())
{
//正確,set_iter只讀
cout << *set_iter << ends;
//錯誤,不能通過指標修改關鍵字
(*set_iter) += 1;
set_iter++;
}
雖然set中同時定義了iterator與const_iterator,但它們都是隻讀的。
2.2.2 關聯容器的插入操作
map
首先我們要明確,插入map中的應該是一個鍵值對,也就是一個初始化了的pair型別的變數。下面給出了4中方法可以初始化pair型別,並插入到map中去。
map<string, string> word_count{{ "1.1","1.2" },{ "2.1","2.2" }};
//使用花括號初始化pair,然後插入到word_count中。
word_count.insert({ "3.1","3.2" });
//使用make_pair返回一個初始化了的pair。
word_count.insert(make_pair("4.1", "4.2"));
//直接定義一個初始化了的pair。
word_count.insert(pair<string, string>("5.1", "5.2"));
//直接定義一個map::value_type的pair型別。
word_count.insert(map<string, string>::value_type("6.1", "6.2"));
for (auto i : word_count)
cout << i.first << ends << i.second << endl;
//執行結果:
// 1.1 1.2
// 2.1 2.2
// 3.1 3.2
// 4.1 4.2
// 5.1 5.2
// 6.1 6.2
set
//定義一個vector容器,包含重複資料。
vector<int> ivec = { 1,2,3,4,4,5,5 };
//遍歷結果1 2 3 4 4 5 5
for (auto i : ivec)
cout << i << ends;
cout << endl;
//定義一個set關聯容器
set<int> set_ivec;
//使用insert插入操作,使用初始化列表
//set的特性去重。
set_ivec.insert({6,6,7,8});
//遍歷結果6 7 8
for (auto i : set_ivec)
cout << i << ends;
cout << endl;
//使用insert插入操作的另一個版本,使用一對迭代器插入
//set的特性排序。
set_ivec.insert(ivec.begin(), ivec.end());
//遍歷結果1 2 3 4 5 6 7 8
for (auto i : set_ivec)
cout << i << ends;
cout << endl;
insert的返回的值依賴於容器的型別和引數。對於map和set,新增單一元素的時候,返回的結果是一個pair,其第一個資料成員是一個迭代器,根據關鍵字,指向一個鍵值對;第二個資料成員是一個bool值,如果新增成功則為true,如果該關鍵字已經在容器中了則返回false。
經典單詞計數程式
//統計每個單詞在輸入中出現的次數。
map<string, size_t> word_count;
string word;
while (cin >> word)
{
//若word已經在word_count中則insert什麼也不做
auto ret = word_count.insert({ word,1 });
if (!ret.second)
++ret.first->second;
//上句等價於++((ret.first)->second)
}
//ret是一個pair
//ret.first是pair的第一個資料成員,是一個map的迭代器,根據關 鍵字指向鍵值對。
//ret.first->second是指向的那個鍵值對的第二個資料成員,在程式中是size_t型別,表示計數器。
multiset與multimap
對於允許重複關鍵字的容器,插入操作如下。
multimap<string,string> authors;
authors.insert({"John","Sot-weed"});
authors.insert({"John","Lost"});
2.2.3 關聯容器的刪除操作
對於刪除操作,關聯容器定義了三個版本的erase。
名稱 | 說明 |
---|---|
c.erase(k) | 從c中刪除關鍵字為k的元素,返回一個size_type值,指明刪除元素的數量 |
c.erase(p) | 從c中刪除迭代器p指定的元素,返回一個指向p之後元素的迭代器,若p指向了c中的尾元素則返回c.end() |
c.erase(b,e) | 刪除迭代器從b到e範圍內的元素,返回e |
2.2.4 map的下標操作
僅map與unordered_map提供了下標運算以及一個隊形的at函式,其他的關聯容器沒有提供。
map<string, size_t> word_count;
//使用下標運算子
//如果該關鍵字已經在容器中,則獲取關鍵字對應的值
//如果該關鍵字不在容器中,則將一個新的鍵值對插入到容器,並將值初始化
word_count["Anna"] = 1;
//這裡提取出新插入的元素,並將值賦值為1
名稱 | 說明 |
---|---|
c[k] | 返回關鍵字為k的值,如果k不在c中,則新增一個關鍵字為k的鍵值對,並對其值進行初始化 |
c.at(k) | 訪問關鍵字為k的元素,如果k不在c中,則丟擲異常 |
2.2.5 關聯容器的訪問操作
名稱 | 說明 |
---|---|
c.find(k) | 返回一個迭代器,指向第一個關鍵字為k的元素,若k不在容器中,則返回c.end() |
c.count(k) | 返回關鍵字為k的元素的數量,對於無重複關鍵字的容器,返回值永遠為0或1 |
c.lower_bound(k) | 返回一個迭代器,指向第一個關鍵字不小於k的元素 |
c.upper_bound(k) | 返回一個迭代器,指向第一個關鍵字大於k的元素 |
c.equal_range(k) | 返回一個迭代器pair,表示關鍵字等於k的元素的範圍,如果k不在c中,pair的兩個成員都等於c.end() |
在multimap或multiset中查詢元素
如果一個multimap或multiset中有多個元素具有相同關鍵字,這些元素會在容器中相鄰儲存。解決這種情況下的查詢問題可以有三種方法。
- 1. 常用的方法是使用count方法,記錄下元素的數量,使用find找到首個值,然後使用迭代器遞加遍歷。
- 2. 可以使用lower_bound與upper_bound 兩種方法,完成相同的工作。
- 3. 使用equal_range()函式,此函式接受一個關鍵字,返回一個迭代器pair,若關鍵字存在,則pair的第一個資料成員指向第一個與關鍵字匹配的元素,第二個資料成員指向最後一個和關鍵字匹配的元素之後的位置。
對於無序容器如果有機會在做補充,如有不對的地方歡迎大家指正交流。