CMap如何使用?用法舉例

       現在,我們來學習MFC中,最常用的數據結構中的最後一個CMap模板。之前,我們已經依次學完了CArray,CList,並且也對它們進行了初步的剖析。

其實,我一直認爲CMap是最簡單的一個數據類型,如果說,大家對這個數據類型產生不良感覺的話,大多是因爲對Hash表的陌生。

    顯然,CMap就是對Hash表的一種實現。對於Hash表來說,我們需要提供成對的Key與Value進行操作,其實,也就是將我們日常使用的數組下標替換成現在Key,至於MFC是採用了什麼樣的散列函數,我們不必知道。

    Hash表可以認爲是數組的一種優化,或者說是對數組缺陷的一種彌補,因爲我們知道,數組在具備了高效存取性能的同時,無法動態的調整自身的大小,又嚴重的影響了它的使用效果。這給了Hash表可乘之機,Hash表總是使用了某種算法儘可能的來達到將成對的元素存儲到一個額定的離散的內存空間,它既繼承了鏈表對自身的動態調整,又儘可能的使讀寫維持在高速的水平,當然無論如何還是要比數組慢的多。

    如果你非要讓我告訴你,Hash表是什麼樣的一個數據結構的話,很遺憾,我無法準確的描述,這就相當於你問我“鳳凰是什麼樣子”,不過我可以告訴你孔雀的樣子。常用的Hash表非常像一個十字數組,似乎十字數組又成爲了衆多讀者的障礙,如果你暫時還不能理解的話,請你去翻閱Hash表的詳細論述,當然你也可以在不久之後,在本處看到這些經典數據結構的精講。

    現在,我們來看一個CMap的用法,至於它的參數,你可以看本空間一篇專門描述CArray,CList以及CMap參數用法的文章《CArray,CList,CMap如何實化(實例化)》。下面是我自己編寫的例子:

    class Point

    {

    public:

         Point()

        {

            m_x = 0;

            m_y = 0;

        }

        Point(int x, int y)

       {

            m_x = x;

            m_y = y;

        }

    public:

        int m_x;

        int m_y;

    };

    typedef CMap<const char*, const char*, Point, Point&>     CMapPnt; //請在使用之前定義

    int main()

    {

        Point elem1(1, 100), elem2(2, 200), elem3(3, 300), point;

        CMapPnt mp;

        // insert 3 elements into map,          #1

        mp.SetAt("1st", elem1);

        mp.SetAt("2nd", elem2);

        mp.SetAt("3th", elem3);

        // search a point named "2nd" from map                  #2

        mp.Lookup("2nd", point);

        printf("2nd: m_x: %d, m_y: %d\n", point.m_x, point.m_y);

        // insert a new pair into map      #3

        Point elem4(4, 400);

        mp["4th"] = elem4;

        cout<<"count: "<<mp.GetCount()<<endl;

        // traverse the entire map                    #4

        size_t index = 0;

        const char* pszKey;

        POSITION ps = mp.GetStartPosition();

        while( ps )

        {  

            mp.GetNextAssoc(ps, pszKey, point);

            printf("index: %d, m_x: %d, m_y: %d\n", ++index, point.m_x, point.m_y);

        }

 

        return 0;

    }

    代碼中,我已經給出了一些註釋,我同樣建議讀者們,用英文在代碼中註釋,這樣的好處實在是太多了。尤其在代碼需要在不同編碼的操作系統上調試的時候。

    對於CMap這個類,我不得不着重囉嗦一下的是:遍歷操作以及取下標“[]”操作,當然還有那個令很多人困惑不已的ARG_KEY到底應該如何選擇的問題。

遍歷,看註釋#4,至於POSITION的含義,請在本空間,查看其它文章。先使用GetStartPosition()函數獲得表頭的位置,然後,我們可以使用GetNextAssoc函數來遍歷。GetNextAssoc(POSITION& rNextPosition, KEY& rKey, VALUE& rValue)函數的參數值得說明一下,大家看到,3個參數都是引用,而第一個是rNextPosition,顧名思義,在函數返回之後,它將會指像下一個元組,當然這是在表還未遍歷完的時候,否則,它將被置爲空(NULL)。

    “[]”,利用下標取元素的這個操作符,在CMap中被重載,用來返回指定Key值數據的引用,不過在註釋#3處,對於先取"4th"這個Point的引用然後賦值的用法,看起來,似乎有點聰明過了頭,因爲在這之前,我們還沒有插入"4th"所對應的元組,但是,程序卻能正常的運行!爲什麼?其實,這樣的用法是十分正確的,因爲CMap畢竟不是數組,它是沒有邊界的,當CMap在獲得一個它無法查詢到的Key值的時候,它會將這個Key以及一個空的數據類型追加到Hash表中去,從而保證了上面的程序可以無誤的運行。

    我們已經說過,ARG_KEY是作爲類型參數傳入CMap的,但並不是任何類型都可以作爲ARG_KEY傳入的。爲什麼?看樣子,這次不得不簡單的說說Hash表的散列函數了。每個Hash表,總會使用一些散列函數,用來查找Key所對應的Value,理想狀態下,我們當然希望Hash表,就是一個數組,雖然這不可能,不過這樣理解,可以幫助我們更好的理解Hash表的物理結構,就讓我們暫時把它看成一個數組吧。數組總是使用下標來直接獲取元素的存儲地址,而下標,顯然應該是個非負整數,從而Hash表,也應該具備這樣的特性,至少必須存在某種算法可以使傳入的Key可以直接的轉化爲一個非負的整數,這也就是ARG_KEY的選擇標準。從而對象、引用無論如何都不應該作爲ARG_KEY成爲CMap的類型參數,而int、unsigned int、指針以及地址就成爲了ARG_KEY的常用類型參數,其實也就是那些類似於整型的數據類型。常常看到一些人在用CMap的時候,試圖使用CString作爲CMap中ARG_KEY的類型參數,這是應該被糾正的方向性錯誤,但有些人似乎會理直氣壯的反駁我,因爲他們發現類型參數KEY是可以使用CString的,這很奇怪嗎?我說過KEY不能使用CString嗎?之所以KEY可以使用CString而ARG_KEY卻用的是LPCTSTR,那是因爲CString重載了operator==(const char*)這個判等操作符,當CMap從Hash表中獲得KEY之後,它會將ARG_KEY與KEY直接相比較。真正存於CMap內部的是KEY,也就是CString。這也就是爲什麼,我們經常會看到CMap被實化成CMap<CString, LPCTSTR/*相當於const char*,非Unicode情況下*/, CString,CString&>這樣的一個四不像實化類的原因,至於CMap的效率優化問題,我們會在以後的文章中繼續與大家探討。

    CMap的確是個很不錯的數據結構,尤其在你建立一個字典的時候。比如idealsoft的含義是"曳光科技",這就是一個元組,也就是一個Pair,Key是"idealsoft",而Value是"曳光科技"。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章