1. 程式人生 > >C++ map容器和multimap容器(STL map容器)

C++ map容器和multimap容器(STL map容器)

目錄

1. 關聯容器和map容器概述

2. map容器

2.1 map的建立以及初始化列表

2.2 map容器的一般常用屬性(方法)

2.3 插入資料

2.4 資料的訪問和遍歷

2.5 資料的刪除

2.6 map中關鍵詞的排序

3. multimap容器

3.1 訪問元素

3.2 刪除元素


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 個版本:

  1. 以待刪除兀素的迭代器作為引數,這個函式沒有返回值;
  2. 以一個鍵作為引數,它會刪除容器中所有含這個鍵的元素,返回容器中被移除元素的個數;
  3. 接受兩個迭代器引數,它們指定了容器中的一段元素,這個範圍內的所有元素都會被刪除,這個函式返回的迭代器指向最後一個被刪除元素的後一個位置。

 

參考文章:https://blog.csdn.net/bat603/article/details/1456141