C++ map容器和multimap容器(STL map容器)
目錄
1. 關聯容器和map容器概述
map容器是關聯容器的一種。在關聯容器中,物件的位置取決於和它關聯的鍵的值。鍵可以是基本型別也可以是類型別。關聯容器是與非關聯容器(順序容器)相對應的,順序容器中元素的位置不依賴於元素的值,而是和該元素加入容器時的位置有關。關聯容器的型別有下面八種:
按關鍵字有序儲存元素 map 關聯陣列;儲存關鍵字-值對 set 關鍵字即值,只儲存關鍵字的容器 multimap 關鍵字可以重複出現的map multiset 關鍵字可以重複出現的set 無序關聯容器 unordered_map 用雜湊函式組織的map,無序 unordered_set 用雜湊函式組織的set,無序 unordered_multimap 雜湊組織的map;關鍵字可以重複 unordered_multiset 雜湊組織的set,關鍵字可以重複
map容器有四種,每一種都是由類模板定義的。所有型別的map容器儲存的都是鍵值對的元素。map容器的元素是pair<const K, T>型別的物件,這種物件封裝了一個T型別的物件和一個與其關聯的K型別的鍵。pair元素中的鍵是const,因為修改鍵會擾亂容器中元素的順序。每種map容器的模板都有不同的特性:
1、map容器:map的底層是由紅黑樹實現的,紅黑樹的每一個節點都代表著map的一個元素。該資料結構具有自動排序的功能,因此map內部的元素都是有序的,元素在容器中的順序是通過比較鍵值確定的。預設使用 less<K> 物件比較。
2、multimap容器:與map容器類似,區別只在於multimap容器可以儲存鍵值相同的元素。
3、unordered_map容器:該容器的底層是由雜湊(又名雜湊)函式組織實現的。元素的順序並不是由鍵值決定的,而是由鍵值的雜湊值確定的,雜湊值是由雜湊函式生成的一個整數。利用雜湊函式,將關鍵字的雜湊值都放在一個桶(bucket)裡面,具有相同雜湊值的放到同一個桶。unordered_map內部元素的儲存是無序的,也不允許有重複鍵值的元素,相當於java中的HashMap。
4、unordered_multimap容器:也可以通過鍵值生成的雜湊值來確定物件的位置,但是它允許有重複的元素。
map和multimap容器的模板都定義在map標頭檔案中,unordered_map和unordered_multimap容器的模板都定義在unordered_map標頭檔案中中。
- multi字首表明鍵值不必唯一,但是如果沒有這個字首,鍵值必須唯一。
- unordered字首表明容器中元素的位置是通過其鍵值所產生的雜湊值來決定的,而不是通過比較鍵值決定的,即容器中的元素是無序的。如果沒有這個字首,則容器中元素是由比較鍵值決定的,即有序。
2. map容器
下圖展示了一個用名稱作為鍵值 map<K, T>容器,物件是整數,用來表示年齡。
前面講過map容器的底層是由紅黑樹(一種非嚴格意義上的平衡二叉樹)實現的,元素檢索的時間複雜度是O(logN),相比於順序檢索的線性時間複雜度還是很快的。
2.1 map的建立以及初始化列表
map類模板有四個型別引數,但是一般只需要指定前兩個模板引數的值。第一個是鍵值的型別,第二個是所儲存物件的型別。我們通常所用的一種構造一個map物件的方法是:
Map<string, int> mapStudent;
當初始化一個map時,必須提供關鍵字型別和值型別。我們將每個關鍵字-值對包圍在花括號中: {key,value} 來指出它們一起構成了map中的一個元素。初始化列表有兩種方式:
map<string, string> authors = { {"Joyce", "James"},
{"Austen", "Jane"},
{"Dickens", "Charles"} };
或者:
map<string, string> authors { {"Joyce", "James"}, {"Austen", "Jane"}, {"Dickens", "Charles"} };
但是需要注意的是:初始化列表的方式是C++11的新特性,對版本比較早的編譯器不支援這一特性。
2.2 map容器的一般常用屬性(方法)
size 返回有效元素個數
max_size 返回容器支援的最大元素個數
empty 判斷容器是否為空,為空是返回true,否則返回false
clear 清空map容器
2.3 插入資料
在構造map容器後,我們就可以往裡面插入資料了。這裡講三種常見的資料插入方法。
第一種:用insert函式插入pair資料
map<string, int> mapStudent;//建立map
mapStudent.insert(pair<string, int>("student_one", 22));
mapStudent.insert(pair<string, int>("student_two", 25));
mapStudent.insert(pair<string, int>("student_three", 21));
或者使用make_pair
map<string, int> mapStudent;
mapStudent.insert(make_pair("student_one", 22));
mapStudent.insert(make_pair("student_two", 25));
mapStudent.insert(make_pair("student_three", 21));
第二種:用insert函式插入value_type資料
map<string, int> mapStudent;//建立map
mapStudent.insert(map<string, int>::value_type("student_one", 22));
mapStudent.insert(map<string, int>::value_type("student_two", 25));
mapStudent.insert(map<string, int>::value_type("student_three", 21));
第三種:用陣列方式插入資料
map<string, int> mapStudent;//建立map
mapStudent["student_one"] = 22;
mapStudent["student_two"] = 25;
mapStudent["student_three"] = 21;
第四種:用emplace函式插入資料
map<string, int> mapStudent;
mapStudent.emplace("student_one", 22);
mapStudent.emplace("student_two", 25);
mapStudent.emplace("student_three", 21);
這幾種插入方法的區別:
- 用insert函式的效果是一樣的,在資料的插入上涉及到集合的唯一性這個概念,即當map中有某個關鍵字時,insert操作是失敗的。
- emplace插入效果跟insert效果一樣。
- 用陣列方式插入資料就不同,它可以覆蓋以前該關鍵字對應的值。
2.4 資料的訪問和遍歷
map訪問和查詢元素的常用方法有:
==========================================================================================
operator[] 訪問元素,也可以用於修改某個元素的value值;不進行下標(關鍵字)是否存在的檢查(即如果關鍵字不存在,程式執行不會出錯),訪問到某個元素時,
如果該元素的鍵值不存在則直接建立該元素,返回是初始的值(比如int型的初始為0,則返回0,string初始為NULL,則返回NULL)
at 訪問元素,也可以用於修改某個元素的value值;會進行下標(關鍵字)是否存在的檢查,如果關鍵字不存在,則會丟擲 out_of_range 異常。
==========================================================================================
利用迭代器訪問元素
******************************************************************************************
map<K, T>::iterator it;
(*it).first; // the key value (of type Key)
(*it).second; // the mapped value (of type T)
(*it); // the "element value" (of type pair<const Key,T>)
元素的鍵值和value值分別是迭代器的first和second屬性。也可以用迭代器指標直接訪問。
it->first; // same as (*it).first (the key value)
it->second; // same as (*it).second (the mapped value)
*****************************************************************************************
迭代器的成員函式:
begin 返回指向容器起始位置的迭代器(iterator)
end 返回指向容器末尾位置的迭代器
cbegin 返回指向容器起始位置的常迭代器(const_iterator)
cend 返回指向容器末尾位置的常迭代器(當不需要對元素進行寫訪問時使用常迭代器)
rbegin 返回指向容器起始位置的反向迭代器(reverse_iterator)
rend 返回指向容器末尾位置的反向迭代器
#########################################################################################
查詢某鍵值的元素是否存在:
count 若存在,返回1;不存在返回0
find 若存在返回指向元素的迭代器指標,不存在返回end()
map的遍歷
① 使用前向迭代器遍歷map
//使用前向迭代器遍歷map
map<string, int>::iterator iter;
for (iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
cout << iter->first << " " << iter->second << endl;
//當然也可以使用迭代器指標解引用的形式訪問
for (map<string, int>::iterator iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
cout << (*iter).first << " " << (*iter).second << endl;
當然,這兩種形式的訪問都是可以改變元素中的value值的,即可以進行寫訪問。例子如下:
map<string, int> mapStudent;//建立map
mapStudent["student_one"] = 22;
mapStudent["student_two"] = 25;
mapStudent["student_three"] = 21;
map<string, int>::iterator iter;
for (iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
(*iter).second = 100; //將mapStudent中的元素value值全部改為100
for (iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
cout << iter->first << " " << iter->second << endl; //遍歷
/*
輸出結果如下:
student_one 100
student_three 100
student_two 100
*/
如果改用const_iterator,則兩種方式都不能進行寫訪問。
② 使用反向迭代器遍歷map
map<string, int>::reverse_iterator iter;
for (iter = mapStudent.rbegin(); iter != mapStudent.rend(); iter++)
cout << iter->first << " " << iter->second << endl; //反向遍歷
反向迭代器,顧名思義,就是倒著訪問容器中元素的。
③ 使用auto關鍵字
C++中auto關鍵字具有自動進行型別檢查的功能,可以少些不少程式碼。比如可以將①中的程式碼直接改為:
for (auto it = mapStudent.begin(); it != mapStudent.end(); it++)
cout << it->first << " " << it->second << endl; //遍歷
當然你還可以使用範圍for語句更簡練的遍歷map容器:
for (auto x : mapStudent){
cout << x.first << " " << x.second << endl;
}
如果需要對元素進行寫操作的話,迴圈變數x必須要宣告成引用型別。
④ 使用陣列的方式遍歷陣列,略~
2.5 資料的刪除
map容器中刪除一個元素要使用erase函式,只要有以下幾種用法。
//使用關鍵字刪除
int res = mapStudent.erase("student_one"); //刪除成功返回1,失敗返回0;只有使用關鍵字刪除時才有返回值
cout << "res=" << res << endl;
//使用迭代器刪除
map<string, int>::iterator iter;
iter = mapStudent.find("student_two");
mapStudent.erase(iter);
//使用迭代器刪除一個範圍的元素
auto it = mapStudent.begin();
mapStudent.erase(it, mapStudent.find("student_two"));
2.6 map中關鍵詞的排序
STL map中過載operator()<的運算子,預設是採用小於號來排序的,以上程式碼在排序上是不存在任何問題的,因為上面的關鍵字是string類型,它本身支援小於號運算。string型別的關鍵字排序是按照字串中的字元的順序,如果第一個字元相同就比較第二個,如果前兩個都相同就比較第三個,以此類推..... 如果關鍵詞是int型的話,就直接按照大小排序了。
在一些特殊情況,比如關鍵字是一個結構體,涉及到排序就會出現問題,因為它沒有小於號操作,insert等函式在編譯的時候過不去,下面給出兩個方法解決這個問題。
第一種:小於號過載,程式舉例
#include <iostream>
#include <string>
#include <map>
using namespace std;
typedef struct tagStudentInfo
{
int nID;
string strName;
}StudentInfo, *PStudentInfo; //學生資訊
int main()
{
int nSize;
//用學生資訊對映分數
map<StudentInfo, int>mapStudent;
map<StudentInfo, int>::iterator iter;
StudentInfo studentInfo;
studentInfo.nID = 1001;
studentInfo.strName = "student_one";
mapStudent.insert(pair<StudentInfo, int>(studentInfo, 90));
studentInfo.nID = 1002;
studentInfo.strName = "student_two";
mapStudent.insert(pair<StudentInfo, int>(studentInfo, 80));
for (iter=mapStudent.begin(); iter!=mapStudent.end(); iter++)
cout<<iter->first.nID<<endl<<iter->first.strName<<endl<<iter->second<<endl;
}
/****以上程式是無法編譯通過的,只要過載小於號,就OK了,如下:******/
typedef struct tagStudentInfo
{
int nID;
string strName;
bool operator < (tagStudentInfo const& _A) const
{
//這個函式指定排序策略,按nID排序,如果nID相等的話,按strName排序
if(nID < _A.nID) return true;
if(nID == _A.nID) return strName.compare(_A.strName) < 0;
return false;
}
}StudentInfo, *PStudentInfo; //學生資訊
第二種:仿函式的應用,這個時候結構體中沒有直接的小於號過載,定義一個比較函式,程式說明
#include <iostream>
#include <string>
#include <map>
using namespace std;
typedef struct tagStudentInfo
{
int nID;
string strName;
}StudentInfo, *PStudentInfo; //學生資訊
class sort
{
public:
bool operator() (StudentInfo const &_A, StudentInfo const &_B) const
{
if(_A.nID < _B.nID) return true;
if(_A.nID == _B.nID) return _A.strName.compare(_B.strName) < 0;
return false;
}
};
int main()
{
int nSize;
//用學生資訊對映分數
map<StudentInfo, int>mapStudent;
map<StudentInfo, int>::iterator iter;
StudentInfo studentInfo;
studentInfo.nID = 1001;
studentInfo.strName = "student_one";
mapStudent.insert(pair<StudentInfo, int>(studentInfo, 90));
studentInfo.nID = 1002;
studentInfo.strName = "student_two";
mapStudent.insert(pair<StudentInfo, int>(studentInfo, 80));
for (iter=mapStudent.begin(); iter!=mapStudent.end(); iter++)
cout<<iter->first.nID<<endl<<iter->first.strName<<endl<<iter->second<<endl;
system("pause");
return 0;
}
3. multimap容器
multimap容器儲存的是有序的鍵/值對,但是可以儲存重複的元素。multimap中會出現具有相同鍵值的元素序列。multimap大部分成員函式的使用方式和map相同。因為重複鍵的原因,multimap有一些函式的使用方式和map有一些區別。
3.1 訪問元素
multimap 不支援下標運算子,因為鍵並不能確定一個唯一元素。和 map 相似,multimap 也不能使用 at() 函式。
find函式
multimap 的成員函式 find() 可以返回一個鍵和引數匹配的元素的迭代器。例如:
std::multimap<std::string, size_t> people {{"Ann",25},{"Bill", 46}, {"Jack", 77}, {"Jack", 32},{"Jill", 32}, {"Ann", 35} };
std::string name {"Bill"};
auto iter = people.find(name);
if (iter ! = std::end (people))
std::cout << name << " is " << iter->second << std::endl;
iter = people.find ("Ann");
if (iter != std::end(people))
std::cout << iter->first << " is " << iter->second <<std::endl;
如果沒有找到鍵,會返回一個結束迭代器,所以我們應該總是對返回值進行檢查。第一個 find() 呼叫的引數是一個鍵物件,因為這個鍵是存在的,所以輸出語句可以執行。第二個 find() 呼叫的引數是一個字串常量,它說明引數不需要和鍵是相同的型別。對容器來說,可以用任何值或物件作為引數,只要可以用函式物件將它們和鍵進行比較。最後一條輸出語句也可以執行,因為有等於 "Ann" 的鍵。事實上,這裡有兩個等於 "Ann" 的鍵,你可能也會得到不同的執行結果。
equal_range
如果我們想訪問給定鍵對應的所有元素。成員函式 equal_range() 就可以做到這一點。它會返回一個封裝了兩個迭代器的 pair 物件,這兩個迭代器所確定範圍內的元素的鍵和引數值相等。例如:
auto pr = people.equal_range("Ann");
if(pr.first != std::end(people))
{
for (auto iter = pr.first ; iter != pr.second; ++iter)
std:cout << iter->first << " is " << iter->second << std::endl;
}
equal_range() 的引數可以是和鍵同類型的物件,或是不同型別的但可以和鍵比較的物件。返回的 pair 物件的成員變數 first 是一個迭代器,它指向第一個大於等於引數的元素;如果鍵和引數相等的元素存在的話,它是第一個鍵和引數相同的元素。如果鍵不存在,pair 的成員變數 first 就是容器的結束迭代器,所以應該總是對它們進行撿查。
lower_bound 和 upper_bound
multimap 的成員函式 lower_bound() 會返回一個迭代器,它指向鍵值和引數相等或大於引數的第一個元素,或者指向結束迭代器。upper_bound() 也返回一個迭代器,它指向鍵值大於函式引數的第一個元素,如果這樣的元素不出現的話,它就是一個結束迭代器。所以,當存在一個或多個相等鍵時,這些函式會返回一個開始迭代器和一個結束迭代器,它們指定了和引數匹配的元素的範圍,這和 equal_range() 返回的迭代器是相同的。因而前面的程式碼段可以這樣重寫:
auto iter1 = people.lower_bound("Ann");
auto iter2 = people.lower_bound("Ann");
if(iter1 != std::end(people))
{
for(auto iter = iterl ; iter != iter2; ++iter)
std::cout << iter->first << " is " << iter->second << std::endl;
}
它和前一個程式碼段的輸出結果是相同的。
count函式
通過呼叫 multimap 的成員函式 count() 可以知道有多少個元素的鍵和給定的鍵相同。
3.2 刪除元素
erase函式
multimap 的成員函式 erase() 有 3 個版本:
- 以待刪除兀素的迭代器作為引數,這個函式沒有返回值;
- 以一個鍵作為引數,它會刪除容器中所有含這個鍵的元素,返回容器中被移除元素的個數;
- 接受兩個迭代器引數,它們指定了容器中的一段元素,這個範圍內的所有元素都會被刪除,這個函式返回的迭代器指向最後一個被刪除元素的後一個位置。