C++|STL map的operator[]的底層實現及其使用

首先我們都知道STL中的map的operator[]可以通過key值找到對應的value值的:

<code>#include <iostream>
#include

using namespace std;

int main()
{
map m;
m.insert(pair(1,11));
m.insert(make_pair(2,22));
m.insert(map::value_type(3,33));
m[4]=44;
for(map::iterator it=m.begin();it!=m.end();it++)
{
//cout<first<second<<endl> }
for(int i=1; i<=4; i++)
{
cout< }
cin.get();

return 0;
}
/*output:
key:1 value: 11
key:2 value: 22
key:3 value: 33
key:4 value: 44
*//<endl>
/<iostream>/<code>

這裡我們要了解map模板類operator[]的底層實現,首先要了解一下map模板類的數據成員和成員函數。(參考:http://www.cplusplus.com/reference/map/map/)

如:

<code>key_type: The first template parameter (Key)
mapped_type: The second template parameter (T)
value_type: pair<const>

operator[]():Access element (public member function )
insert():Insert elements (public member function )/<const>/<code>

其中operator[]()的實現原型:

(參考:http://www.cplusplus.com/reference/map/map/operator[]/)

<code>mapped_type& operator[] (const key_type& k)
{
return (*((this->insert(make_pair(k,mapped_type()))).first)).second;
}/<code>

那我們可以發現其實operator[]底層是用insert()來實現的。

(參考:http://www.cplusplus.com/reference/map/map/operator[]/)

那麼inset()的返回值又是什麼呢?我們一起來看看insert()的函數原型:

<code>pair<iterator> insert (const value_type& val);/<iterator>/<code>

cplusplus網站對insert的返回值是這樣解釋的:

The single element versions (1) return a pair, with its member pair::first set to an iterator pointing to either the newly inserted element or to the element with an equivalent key in the map. The pair::second element in the pair is set to true if a new element was inserted or false if an equivalent key already existed.

inset的返回值其實是一個pair,這個pair的key值其實是一個迭代器,這個迭代器是指向新插入這個位置的value_type,value值其實是一個bool值,表示插入成功還是失敗,原來沒有這個key值插入會成功返回true,已經存在這個key值插入會失敗,返回false。

我們再回頭看看這個operator[]的底層實現:

C++|STL map的operator[]的底層實現及其使用

make_pair(k,mapped_type())(紅色框框):表示創建了一個pair,這個pair的key值為傳參傳過來的k,value是一個mapped_type的value,暫時為空待定。

insert(make_pair(k,mapped_type()))(綠色框框):調用insert插入這個pair。插入這個pair會返回一個pair。pair的第一個值就是這個位置的迭代器,但是因為這個key值已經存在了,因此pair的value為false。

(this->insert(make_pair(k,mapped_type()))).first(粉色框框):this指針取到這個pair的first,就是我們剛剛看到的insert的返回值pair的key值,也就是插入位置的迭代器。

最後解引用迭代器之後再取到這個位置pair的second值,也就是這個位置的value值。然後函數的返回值為mapped_type的引用,因為是引用,所以可以做為左值而更新其value。

綜上所述,operator[]返回的是這個key值所對應的value的引用。因此operator[]可以通過key值找到對應的value值,然後對這個value值進行修改操作。

舉個例子:

<code>map<string> dict;
dict.insert({ "left", "左邊" });
dict["left"] = "剩餘";/<string>/<code>

“left”這個key值已經存在,因此insert返回的是這個位置的迭代器和一個false(false是因為key值存在插入失敗),取到這個迭代器,然後解引用取到迭代器的second,也就是value並返回value的引用,因此dict["left"]返回的就是"左邊"這個字符串的引用,那麼在執行賦值操作,也就是改變了這個“left”所對應的value值,此時“left”的value值就變成了“剩餘”。

那麼operator[]又可以完成數據的插入,這又是怎麼實現的呢?

我們繼續分析operator[]的底層實現。

C++|STL map的operator[]的底層實現及其使用

當我們傳過來的key值是map裡沒有的,那麼這裡創建了一個pair,這個pair的key值就是傳過來的key值,但是此時這裡的mapped_type的value,暫時為空待定。調用了insert,因為這個key值沒有,所以完成了插入,insert的返回值為新插入這個位置的迭代器和true(true表示插入成功),然後取到這個迭代器,解引用之後拿到這個key值的value(此時value值為空),返回的就是這個空的value。

再舉個例子:

<code>map<string> dict;
dict["banana"];/<string>/<code>

如上就是插入了一個key為“banana”,value為“\\0”的一個pair。

<code>dict["key"] = "關鍵字";/<code>

如上,首先經過dict["key"]之後,就成功插入了一個key值為“key”,value為“\\0”的一個pair,返回值是“\\0”的引用,然後再賦值操作,也就是將“\\0”變成了“關鍵字”,這樣就完成了一個pair的插入。

總結一下,operator[]可以查找到對應key值的value,如果key值沒有的話,也可以進行插入操作。

實例:統計每種水果出現的次數。

“蘋果”, “蘋果”, “梨”, “蘋果”, “香蕉”, “梨”, “香蕉”, “香蕉”, “蘋果”, "西瓜"

有以下三種解決方法:

方法一:

定義一個map,類型為map<string> ,key值就是水果(不允許重複),value值就可以記錄出現的次數。/<string>

去尋找字符串在不在這個map中,如果這個水果在,那麼直接給這個pair的second++,如果不在,就插入這個水果,把second設置為1。

<code>string str[] = { "蘋果", "蘋果", "梨", "蘋果", "香蕉", "梨", "香蕉", "香蕉", "蘋果", "西瓜" };
map<string> count_map;
for (const auto &s : str)
{
\tauto tmp = count_map.find(s);
\tif (tmp != count_map.end())
\t{
\t\ttmp->second++;
\t}
\telse
\t{
\t\tcount_map.insert({ s, 1 });//返回的是pair::iterator ,bool>
\t}
}
for (const auto &e : count_map)
{
\tcout << e.first << ":" << e.second << endl;
}
/<string>/<code>

方法二:

定義一個map,類型為map<string> ,key值就是水果(不允許重複),value值就可以記錄出現的次數。/<string>

因為insert的返回值中value是一個bool,表示插入成功還是失敗,所以我們直接插入這個水果和1(表示這個水果出現了一次)。我們可以通過這個bool值判斷這個水果在不在這個map裡。如果在,那麼insert的返回值的value是一個false,那麼直接利用inset的返回值中的key(對應位置的迭代器)找到對應位置的value,直接++即可。如果不在那麼insert的返回值的value是一個true,此時就完後了這個水果和1次的插入。

<code>string str[] = { "蘋果", "蘋果", "梨", "蘋果", "香蕉", "梨", "香蕉", "香蕉", "蘋果", "西瓜" };
map<string> count_map;
for (const auto &s : str)
{
// insert()的返回值是一個pair
\tpair::iterator ,bool> ret = count_map.insert(pair<string>(s, 1));
\tif (!ret.second)
\t{
\t\t(ret.first)->second++;
\t}
}
for (const auto &e : count_map)
{
\tcout << e.first << ":" << e.second << endl;
}/<string>
/<string>/<code>

方法三:

定義一個map,類型為map<string> ,key值就是水果(不允許重複),value值就可以記錄出現的次數。/<string>

我們可以利用這個operator[]來解決這個問題,代碼非常簡單。

直接遍歷這個水果字符串數組,然後調用operator[],因為operator[]有就返回value的引用,沒有就插入之後返回value的引用,那麼當遍歷這些水果的時候,如果這個水果在,那麼直接給value++,如果這個水果不在,那麼返回的是0的引用,直接++就變成了1,表示出現一次,後續在插入同樣的水果時,插入失敗,直接給value++,邏輯也沒有任何問題,因此這個方法最為簡單,推薦~

<code>string str[] = { "蘋果", "蘋果", "梨", "蘋果", "香蕉", "梨", "香蕉", "香蕉", "蘋果", "西瓜" };
map<string> count_map;
for (auto s : str)
{
\tcount_map[s]++;
}
for (const auto &e : count_map)
{
\tcout << e.first << ":" << e.second << endl;
}/<string>/<code>

三種方法都可以正確運行,結果如下:

<code>梨:2
蘋果:4
西瓜:1
香蕉:3/<code>

ref

https://blog.csdn.net/ETalien_/article/details/89442131

-End-


分享到:


相關文章: