天天看點

背景開發:核心技術與應用實踐3.4.2 map的查增删

<b>3.4.2 map的查增删</b>

<b></b>

<b>1.?map的插入</b>

先講下map的插入,map的插入有3種方式:用insert函數插入pair資料、用insert函數插入value_type資料和用數組方式插入資料。

【例3.18】 用insert函數插入pair資料。

#include

&lt;map&gt;

&lt;string&gt;

&lt;iostream&gt;

using namespace

std;

int main()

{

    map&lt;int, string&gt; mapstudent;

    mapstudent.insert(pair&lt;int,

string&gt;(1, "student_one"));

string&gt;(2, "student_two"));

string&gt;(3, "student_three"));

    map&lt;int, string&gt;::iterator iter;

    for(iter = mapstudent.begin(); iter !=

mapstudent.end(); iter++){

       cout&lt;&lt;iter-&gt;first&lt;&lt;"

"&lt;&lt;iter-&gt;second&lt;&lt;endl;

    }

    return 0;

}

程式的執行結果是:

1 student_one

2 student_two

3 student_three

例3.18中,聲明了一個key為int類型,value為string類型的map,用insert函數插入pair資料,且需要在insert的參數中将(1, "student_one")轉換為pair資料再進行插入。

【例3.19】 用insert函數插入value_type資料。

    mapstudent.insert(map&lt;int,

string&gt;::value_type (1,"student_one"));

    mapstudent.insert(map&lt;int, string&gt;::value_type

(2,"student_two"));

string&gt;::value_type (3,"student_three"));

    map&lt;int, string&gt;::iterator  iter;

例3.19中,聲明了一個key為int類型,value為string類型的map,用insert函數插入value_type資料,且需要在insert的參數中将(1, "student_one")轉換為map&lt;int, string&gt;::value_type資料再進行插入。

【例3.20】 map中用數組方式插入資料。

    #include &lt;map&gt;

    #include &lt;string&gt;

int main(){

    mapstudent[1] = "student_one";

    mapstudent[2] = "student_two";

    mapstudent[3] = "student_three";

cout&lt;&lt;iter-&gt;first&lt;&lt;"

1  student_one

2  student_two

3  student_three

例3.20中展示了用數組方式在map中插入資料,和數組通路一樣,有下标、直接指派。以上3種用法,雖然都可以實作資料的插入,但是它們是有差別的,當然了第一種和第二種在效果上是完成一樣的,用insert函數插入資料,在資料的插入上涉及集合的唯一性這個概念,即當map中有這個關鍵字時,insert操作是插入資料不了的,但是用數組方式就不同了,它可以覆寫以前該關鍵字對應的值。

mapstudent.insert(map&lt;int,

string&gt;::value_type (1, "student_one"));

string&gt;::value_type (1, "student_two"));

上面這兩條語句執行後,map中1這個關鍵字對應的值是student_one,第二條語句并沒有生效,那麼這就涉及如何知道insert語句是否插入成功的問題了,可以用pair來獲得是否插入成功,程式如下:

pair&lt;map&lt;int,

string&gt;::iterator, bool&gt; insert_pair;

insert_pair =

mapstudent.insert(map&lt;int, string&gt;::value_type (1,

"student_one"));

可以通過pair的第二個變量來知道是否插入成功,它的第一個變量傳回的是一個map的疊代器,如果插入成功的話insert_pair.second應該是true的,否則為false。

【例3.21】 用pair判斷insert到map的資料是否插入成功。

    pair&lt;map&lt;int, string&gt;::iterator,

bool&gt; insert_pair;

     insert_pair =

mapstudent.insert(pair&lt;int,string&gt;(1,"student_one"));

    if(insert_pair.second == true){

        cout&lt;&lt;"insert

successfully"&lt;&lt;endl;

    else{

failure"&lt;&lt;endl;

    insert_pair =

mapstudent.insert(pair&lt;int, string&gt;(1, "student_two"));

          cout&lt;&lt;"insert

     }else{

     map&lt;int, string&gt;::iterator iter;

     for(iter = mapstudent.begin(); iter !=

     }

     return 0;

insert

successfully

insert failure

例3.21中,用pair判斷insert到map的資料是否插入成功。pair變量insert_pair中的第一個元素的類型是map&lt;int, string&gt;::iterator,是和即将要判斷的map中的key、value類型一緻的一個map的疊代器。如果insert成功了,則insert_pair.second的結果為true,否則則為false。同一個key已經有資料之後,再insert就會失敗。而數組插入的方式,則是直接覆寫。

【例3.22】 資料方式插入map覆寫原有的資料。

    map&lt;int,string&gt; mapstudent;

    mapstudent[1] =  "student_one";

    mapstudent[1] =  "student_two";

    mapstudent[2] =  "student_three";

1 student_two

2 student_three

例3.22中展示了mapstudent[1]上已經有資料"student_one"了,再用語句:

mapstudent[1]

=  "student_two";

就可以直接覆寫成功。

2.?map的周遊

map資料的周遊,這裡也提供3種方法,來對map進行周遊:應用前向疊代器方式、應用反向疊代器方式和數組方式。應用前向疊代器,上面舉例程式中已經講解過了,這裡着重講解應用反向疊代器的方式,下面舉例說明。

【例3.23】 map反向疊代器的使用舉例。

    mapstudent[2] =  "student_two";

    mapstudent[3] =  "student_three";

     map&lt;int,

string&gt;::reverse_iterator   iter;

     for(iter = mapstudent.rbegin(); iter !=

mapstudent.rend(); iter++){

例3.23中,iter就是一個反向疊代器reverse_iterator,它需要使用rbegin()和rend()方法指出反向周遊的起始位置和終止位置。注意,前向周遊的一般是從begin()到end()周遊,而反向周遊則是從rbegin()到rend()。

【例3.24】 用數組方式周遊map。

#include&lt;map&gt;

#include&lt;string&gt;

#include&lt;iostream&gt;

    int isize = mapstudent.size();

    for(int i = 1; i &lt;= isize; i++){

        cout&lt;&lt;i&lt;&lt;"

"&lt;&lt;mapstudent[i]&lt;&lt;endl;

例3.24中,用size()方法确定目前map中有多少元素。用數組通路vector時,下标是從0~(size-1),而用數組通路map,卻是從1~size,這是有所不同的,請使用者多加注意。

3.?map的查找

在這裡可以充分體會到map在資料插入時保證有序的好處。要判定一個資料(關鍵字)是否在map中出現的方法比較多,這裡給出2種常用的資料查找方法。

第一種:用count函數來判定關鍵字是否出現,其缺點是無法定位資料出現位置,由于map的一對一的映射特性,就決定了count函數的傳回值隻有兩個,要麼是0,要麼是1,當要判定的關鍵字出現時傳回1。

第二種:用f?ind函數來定位資料出現位置,它傳回的一個疊代器,當資料出現時,它傳回資料所在位置的疊代器;如果map中沒有要查找的資料,它傳回的疊代器等于end函數傳回的疊代器。

【例3.25】 用f?ind方法查找map中的資料。

     map&lt;int, string&gt;::iterator

iter=mapstudent.find(1);

     if(iter != mapstudent.end()){

          cout&lt;&lt;"found, the value is

          cout&lt;&lt;"do not

found"&lt;&lt;endl;

find, the value

is student_one

f?ind函數傳回的是一個疊代器;找不到對應資料的時候,則會傳回mapstudent.end()。

4.?map的删除

用erase方法可删除map中的元素。erase的函數原型是:

map.erase(k)

删除map中鍵為k的元素,并傳回size_type類型的值以表示删除的元素個數,代碼如下:

map.erase(p)

從map中删除疊代器p所指向的元素。p必須指向map中确實存在的元素,而且不能等于map.end(),傳回void類型,代碼如下:

map.erase(b,e)

從map中删除一段範圍内的元素,該範圍由疊代器對b和e标記。b和e必須标記map中的一段有效範圍:即b和e都必須指向map中的元素或最後一個元素的下一個位置。而且,b和e要麼相等(此時删除的範圍為空),要麼b所指向的元素必須出現在e所指向的元素之前,傳回void類型。常用的是第二種,并且是在周遊的過程中删除元素。

【例3.26】 用erase方法删除map中的元素。

    mapstudent[1]="student_one";

    mapstudent[2]="student_two";

    mapstudent[3]="student_three";

    mapstudent[4]="student_four";

    map&lt;int, string&gt;::iterator

iter=mapstudent.begin();

    for(;iter!=mapstudent.end();){

if((*iter).second=="student_one"){

            mapstudent.erase(iter++);

        }

        else{

            ++iter;

for(iter=mapstudent.begin();iter!=mapstudent.end();iter++){

4 student_four

mapstudent.erase(iter++);中的iter++,不是erase(iter),然後iter++。因為iter指針被erase之後就失效了,不能再用iter++;也不是erase(++iter),這樣就不是删iter原來指向的元素了。

5.?map的排序

map的排序預設按照key從小到大排序,但有以下幾點需要注意:①按照key從大到小排序;②key(第一個元素)是一個結構體;③想按value(第二個元素)排序。

map是stl裡面的一個模闆類,現在來看下map的定義:

template &lt;

class key, class t, class compare = less&lt;key&gt;,

           class allocator =

allocator&lt;pair&lt;const key,t&gt; &gt; &gt; class map;

它有4個參數,其中比較熟悉的有兩個:key和value。第4個是allocator,用來定義存儲配置設定模型的,此處不作介紹。

現在重點看下第3個參數:

class compare =

less&lt;key&gt;

這也是一個class類型的,而且提供了預設值less&lt;key&gt;。less是stl裡面的一個函數對象,那麼什麼是函數對象呢?

所謂的函數對象,即調用操作符的類,其對象常稱為函數對象(function object),它們是行為類似函數的對象。表現出一個函數的特征,就是通過“對象名+(參數清單)”的方式使用一個類,其實質是對operator()操作符的重載。

現在來看一下less的實作:

template

&lt;class t&gt; struct less : binary_function &lt;t,t,bool&gt; {

    bool operator() (const t&amp; x, const

t&amp; y) const

        {return x&lt;y;}

};

它是一個帶模闆的struct,裡面僅僅對()運算符進行了重載,實作很簡單,但用起來很友善,這就是函數對象的優點所在。stl中還為四則運算等常見運算定義了這樣的函數對象,與less相對的還有greater:

&lt;class t&gt; struct greater : binary_function &lt;t,t,bool&gt; {

        {return x&gt;y;}

是以,要想讓map中的元素按照key從大到小排序,可以如例3.27所示。

【例3.27】 讓map中的元素按照key從大到小排序。

    map&lt;string, int, greater&lt;string&gt;

&gt; mapstudent;

        mapstudent["limin"]=90;

        mapstudent["zilinmi"]=72;

        mapstudent["bob"]=79;

    map&lt;string, int&gt;::iterator

cout&lt;&lt;iter-&gt;first&lt;&lt;" "&lt;&lt;iter-&gt;second&lt;&lt;endl;

        return 0;

zilinmi 72

limin 90

bob 79

如例3.27中所示,隻需指定它的第3個參數compare,把預設的less指定為greater,即可達到按照key從大到小排序。現在知道如何為map指定compare類了,如果想自己寫一個compare的類,讓map按照想要的順序來存儲,比如按照學生姓名的長短排序進行存儲,那麼隻要自己寫一個函數對象,實作想要的邏輯,并在定義map的時候把compare指定為自己編寫的這個就可以實作了,代碼如下:

struct

cmpbykeylength {

    bool operator()(const string&amp; k1, const

string&amp; k2) {

        return k1.length() &lt; k2.length();

這裡不用把compare定義為模闆,直接指定它的參數為string類型就可以了。

【例3.28】 重定義map内部的compare函數。

#include &lt;string&gt;

    map&lt;string, int, cmpbykeylength &gt;

mapstudent;

    mapstudent["limin"]=90;

    mapstudent["zilinmi"]=72;

    mapstudent["bob"]=79;

是以,想改變map的key排序方法,可以通過修改compare函數實作。

key是結構體的,如果沒有重載小于号(&lt;)操作,就會導緻insert函數在編譯時就無法編譯成功。其實,為了實作快速查找,map内部本身就是按序存儲的(比如紅黑樹)。在插入&lt;key, value&gt;鍵值對時,就會按照key的大小順序進行存儲。這也是作為key的類型必須能夠進行&lt;運算比較的原因。

【例3.29】 key是結構體的map排序。

typedef struct

tagstudentinfo

    int iid;

    string 

strname;

    bool operator &lt; (tagstudentinfo

const&amp; r) const {

        // 這個函數指定排序政策,按iid排序,如果iid相等的話,按strname排序

        if(iid &lt; r.iid)  return true;

        if(iid == r.iid) return

strname.compare(r.strname) &lt; 0;

        return false;

}studentinfo;// 學生資訊

    /*用學生資訊映射分數*/

    map&lt;studentinfo, int&gt;mapstudent;

    studentinfo studentinfo;

    studentinfo.iid = 1;

    studentinfo.strname =

"student_one";

    mapstudent[studentinfo]=90;

    studentinfo.iid = 2;

"student_two";

    mapstudent[studentinfo]=80;

    map&lt;studentinfo, int&gt;::iterator

    for(;iter!=mapstudent.end();iter++){

cout&lt;&lt;iter-&gt;first.iid&lt;&lt;"

"&lt;&lt;iter-&gt;first.strname&lt;&lt;"

1 student_one 90

2 student_two 80

例3.29中,mapstudent的key是studentinfo類型的,要重載studentinfo類型的&lt;号,這樣才能正常地插入到mapstudent中。

如果想按照map的value(第二個元素)排序,第一反應是利用stl中提供的sort算法實作,這個想法是好的,不幸的是,sort算法有個限制,利用sort算法隻能對序列容器進行排序,隻能是線性的(如vector、list、deque)。map也是一個集合容器,它裡面存儲的元素是pair,但是它不是線性存儲的(如紅黑樹),是以利用sort不能直接和map結合進行排序。雖然不能直接用sort對map進行排序,那麼可以間接進行,把map中的元素放到序列容器(如vector)中,然後再對這些元素進行排序呢?這個想法看似是可行的。要對序列容器中的元素進行排序,也有個必要條件:就是容器中的元素必須是可比較的,也就是實作了&lt;操作的。那麼現在就來看下map中的元素是否滿足這個條件。

已知map中的元素類型為pair,具體定義如下:

&lt;class t1, class t2&gt; struct pair

    typedef t1 first_type;

    typedef t2 second_type;

    t1 first;

    t2 second;

    pair() : first(t1()), second(t2()) {}

    pair(const t1&amp; x, const t2&amp; y) :

first(x), second(y) {}

    template &lt;class u, class v&gt;

        pair (const pair&lt;u,v&gt; &amp;p) :

first(p.first), second(p.second) { }

pair也是一個模闆類,這樣就實作了良好的通用性。它僅有兩個資料成員f?irst和second,即key和value,而且在&lt;utility&gt;頭檔案中,還為pair重載了&lt; 運算符,具體實作如下所示:

template&lt;class

_t1, class _t2&gt;

    inline bool

    operator&lt;(const pair&lt;_t1,

_t2&gt;&amp; __x, const pair&lt;_t1, _t2&gt;&amp; __y)

    { return __x.first &lt; __y.first

             || (!(__y.first &lt; __x.first)

&amp;&amp; __x.second &lt; __y.second); }

重點看下其實作,如下所示:

__x.first &lt;

__y.first || (!(__y.first &lt; __x.first) &amp;&amp; __x.second &lt;

__y.second)

這個less在兩種情況下傳回true。第一種情況:__x.f?irst &lt; __y.f?irst,這種情況較好了解,就是比較key與__x、__y的大小,如果__x的key小于__y的key則傳回true。

第二種情況有點費解,代碼如下所示:

!(__y.first &lt;

__x.first) &amp;&amp; __x.second &lt; __y.second

當然由于||運算具有短路作用,即目前面的條件不滿足時,才進行第二種情況的判斷。第一種情況__x.f?irst &lt; __y.f?irst不成立,即__x.f?irst &gt;= __y.f?irst成立,在這個條件下,再來分析下!(__y.f?irst &lt; __x.f?irst) 

&amp;&amp; __x.second &lt; __y.second。

!(__y.f?irst

&lt; __x.f?irst)表示y的key不小于x的key,結合前提條件,__x.f?irst &lt; __y.f?irst不成立,即x的key不小于y的key。

即:!(__y.f?irst &lt; __x.f?irst) &amp;&amp;!(__x.f?irst &lt; __y.f?irst

)等價于__x.f?irst ==

__y.f?irst ,也就是說,第二種情況是在key相等的情況下,比較兩者的value(second)。

這裡比較令人費解的地方就是,為什麼不直接寫__x.f?irst == __y.f?irst呢?這麼寫看似費解,但其實也不無道理:前面講過,作為map的key必須實作&lt;操作符的重載,但是并不保證==操作符也被重載了,如果key沒有提供==,那麼__x.f?irst == __y.f?irst的寫法就不對。由此可見,stl中的代碼是相當嚴謹的,值得好好研讀。

現在知道了pair類重載了&lt;符,但是它并不是按照value進行比較的,而是先對key進行比較,key相等時候才對value進行比較。顯然不能滿足按value進行排序的要求。

而且,既然pair已經重載了&lt;符,但不能修改其實作,也不能在外部重複實作重載

&lt;符。

如果pair類本身沒有重載&lt;符,那麼按照下面的代碼重載&lt;符,是可以實作對pair類按value比較的。但現在這樣做不行了,甚至會出錯(編譯器不同,嚴格的就報錯)。

typedef

pair&lt;string, int&gt; pair;

bool

operator&lt; (const pair&amp; lhs, const pair&amp; rhs) {

    return lhs.second &lt; rhs.second;

那麼要如何實作對pair按value進行比較呢?第一種是最原始的方法,寫一個比較函數;第二種剛才用到了,寫一個函數對象,如下所示。這兩種方式實作起來都比較簡單。

cmp_by_value(const pair&amp; lhs, const pair&amp; rhs) {

cmpbyvalue {

    bool operator()(const pair&amp; lhs, const

pair&amp; rhs) {

        return lhs.second &lt; rhs.second;

接下來使用sort算法,來檢驗其是不是像map一樣,也可以對指定的元素進行比較,代碼如下所示:

&lt;class randomaccessiterator&gt;

void sort (

randomaccessiterator first, randomaccessiterator last );

&lt;class randomaccessiterator, class compare&gt;

randomaccessiterator first, randomaccessiterator last, compare comp );

結果表明,sort算法和map一樣,也可以對指定元素進行比較,即指定compare。需要注意的是,map是在定義時指定的,是以傳參的時候直接傳入函數對象的類名,就像指定key和value時指定的類型名一樣;sort算法是在調用時指定的,需要傳入一個對象,當然這個也簡單,類名()就會調用構造函數生成對象。

這裡也可以傳入一個函數指針,就是把上面說的第一種方法的函數名傳過來。

【例3.30】 将map按value排序。

&lt;vector&gt;

    map&lt;string, int&gt; name_score_map;

    name_score_map["limin"] = 90;

    name_score_map["zilinmi"] = 79;

    name_score_map["bob"] = 92;

name_score_map.insert(make_pair("bing",99));

name_score_map.insert(make_pair("albert",86));

    /*把map中元素轉存到vector中*/

    vector&lt;pair&gt;

name_score_vec(name_score_map.begin(), name_score_map.end());

    sort(name_score_vec.begin(),

name_score_vec.end(), cmpbyvalue());

    /*sort(name_score_vec.begin(),

name_score_vec.end(), cmp_by_value);也是可以的*/

    for (int i = 0; i != name_score_vec.size();

++i) {

cout&lt;&lt;name_score_vec[i].first&lt;&lt;"

"&lt;&lt;name_score_vec[i].second&lt;&lt;endl;

例3.30中要對map中的value進行排序,先把map的元素按pair形式插入到vector中,再對vector進行排序(用一個新寫的比較函數),這樣就可以實作按map的value排序了。

繼續閱讀