map自定義鍵值類型


map自定義鍵值類型

 

改變Map的默認比較方式

https://www.cnblogs.com/zjfdlut/archive/2011/08/12/2135698.html

 

大家知道,STL中的map底層是用紅黑樹實現的,其泛型原型如下:

template <class _Key, class _Tp, class _Compare, class _Alloc>
class map {
......
}

其中_Key表示比較的鍵(key),_Tp表示值(value),_Compare表示比較方式,_Alloc表示內存分配器。

一般我們在寫map的時候總是類似於寫出如下代碼:

map<int, char*>* my_map = new map<int, char*>;

表示鍵為int類型,值為字符串類型。這里之所以不對_Compare和_Alloc加以限制,是因為int是C++內置類型,有默認比較方式,_Alloc也采用STL的

默認的內存方案。但是如果有如下結構體:

struct Term{
char* str;
int hashCode;
};

現在我們要將該Term作為map的鍵,並假設Term所對應的值為Term出現的頻率(int型),那么能不能這樣寫:

map<Term, int>* my_map = new map<Term, int>;

顯然這樣寫map是無法正常運作的,原因是struct Term並非C++的內置類型,默認不知道如何去比較它。這時候就需要修改map的默認比較方式:

template <class T>
struct Compare
{
int operator()(const T& x, const T& k) const{
if(x.hashCode >= k.hashCode) return 0;
else return 1;
}
};

這里采用的是函數對象(function object)的方式去加載map的比較方式,表示使用Term的hashCode作為比較方式,以對紅黑樹進行查找、插入等操作。

這樣我們就可以把map寫成下面的形式:

map<Term, int, Compare<Term> >* my_map = new map<Term, int, Compare<Term> >;

這樣map就可以正常運作了,比如進行插入操作:

Term my_term;
my_map->insert(make_pair(my_term, 1));

但是上面的struct Compare為什么要寫成這樣的形式,寫成這樣行不行:

template <class T>
struct Compare
{
int operator()(const T& x, const T& k) const{
if(x.hashCode >= k.hashCode) return 1;
else return 0;
}
};

這是不行的。為什么不行,首先來看一看map中find的源代碼:

template <class _Key, class _Value, class _KeyOfValue, 
class _Compare, class _Alloc>
typename _Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>::iterator
_Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>::find(const _Key& __k)
{
_Link_type __y = _M_header; // Last node which is not less than __k.
_Link_type __x = _M_root(); // Current node.

while (__x != 0)
if (!_M_key_compare(_S_key(__x), __k))
__y = __x, __x = _S_left(__x);
else
__x = _S_right(__x);

iterator __j = iterator(__y);
return (__j == end() || _M_key_compare(__k, _S_key(__j._M_node))) ? end() : __j;
}

上面的代碼中_M_key_compare就表示我們的那個比較函數對象,_S_key(__x)表示取__x節點的key,並和__k比較。

if (!_M_key_compare(_S_key(__x), __k))
__y = __x, __x = _S_left(__x);

表示如果_S_key(__x) >= __k即,如果節點的key大於或等於查找的key那么就__x就等於它的左子節點,否則就為右子節點。

但為什么等於的時候不直接返回呢,卻在繼續查找?舉個例子來說:

如果我們要查找key為10的節點是否在樹中時,首先從根節點開始查找,由於8<10,這時_M_key_compare返回1,那么此時,

轉向root的右子樹,然后由於10==10,_M_key_compare返回0,這時轉向左子樹,但左子樹是空的,循環停止。

 return (__j == end() || _M_key_compare(__k, _S_key(__j._M_node))) ? end() : __j;

由於此時__j表示"10"這個節點(其實是個迭代器),由於__k為10,而__j._M_node的key為10,_M_key_compare返回0,有三元運算符可知,

此時返回是__j,即表示找到了。因此我們的比較函數對象必需寫成:

當節點鍵大於等於所要查找或插入的鍵時,返回0(false),反之為1(true),這是由內部源代碼所決定的。

 

map自定義鍵值類型

原文:https://blog.csdn.net/y109y/article/details/82901710 

 

1. map定義
map是STL里的一個模板類,用來存放<key, value>鍵值對的數據結構,它的定義如下。

template < class Key,                                   //map::key_tpe
           class T,                                     //map::mapped_type
           class Compare = less<Key>,                   //map::key_compare
           class Alloc = allocator<pair<const Key, T>>  //map::allocator_type
           > class map;

 

第1個參數存儲了key。

第2個參數存儲了mapped value。

第3個參數是比較函數的函數對象。map用它來判斷兩個key的大小,並返回bool類型的結果。利用這個函數,map可以確定元素在容器中遵循的順序以及兩個元素鍵是否相等(!comp(a,b)&&!comp(b,a)),確保map中沒有兩個元素可以具有等效鍵。這里,它的默認值是less<Key>,定義如下。

template <class T> 
struct less {
  bool operator() (const T& x, const T& y) const {return x < y;}
  typedef T first_argument_type;
  typedef T second_argument_type;
  typedef bool result_type;
};

 

第4個參數是用來定義存儲分配模型的。

 

2. 簡單方法: 重載operator<()操作符
在我們插入<key, value>時,map會先通過比較函數地函數對象來比對key的大小,然后根據比對結果進行有序存儲。c++標准庫中,map比較函數的函數對象不可避免地會用到’<'運算,因此一種方法就是直接在自定義類里重載operator<()操作符,如下所示。

#include <iostream>
#include <map>
#include <string>
using namespace std;

class Person{
public:
    string name;
    int age;

    Person(string n, int a){
        name = n;
        age = a;
    }

    bool operator<(const Person &p) const //注意這里的兩個const
    {
        return (age < p.age) || (age == p.age && name.length() < p.name.length()) ;
    }
};

int main(int argc, char* argv[]){
    map<Person, int> group;
    group[Person("Mark", 17)] = 40561;
    group[Person("Andrew",18)] = 40562;
    for (auto ii = group.begin() ; ii != group.end() ; ii++)
        cout << ii->first.name 
        << " " << ii->first.age
        << " : " << ii->second
        << endl;
    return 0;
}

 

這里,我們需要注意的是,在重載operator<(){}時,無論是參數還是整個函數的const都不能少。參照less<Key>的定義,less的參數和函數整體都是const,那么被調用的operator<()必然也是同等要求。

 

3. 其它方法:比較函數的函數對象
如果不重載operator<()是不是就不行了?當然不是。除了直接重載operator<(),我們可以直接自定義比較函數的函數對象。

首先簡要介紹一下函數對象的概念:在《C++ Primer Plus》里面,函數對象是可以以函數方式與()結合使用的任意對象。這包括函數名、指向函數的指針和重載了“operator()”操作符的類對象。基於此,我們提出3種定義方法。

 

3.1 方法1: 利用std::function
方法1利用std::function。它是一種通用、多態、類型安全的函數封裝,其實例可以對任何可調用目標實體(包括普通函數、Lambda表達式、函數指針、以及其它函數對象等)進行存儲、復制和調用操作,方法如下。

 

#include <iostream>
#include <map>
#include <string>
#include <functional>
using namespace std;

class Person{
public:
    string name;
    int age;

    Person(string n, int a){
        name = n;
        age = a;
    }
};

bool MyCompare(const Person &p1, const Person &p2) {//普通的函數
    return (p1.age < p2.age) || (p1.age == p2.age && p1.name.length() < p2.name.length());
}

int main(int argc, char* argv[]){
    map<Person, int, function<bool(const Person &, const Person &)>> group(MyCompare); //需要在構造函數中指明
    group[Person("Mark", 17)] = 40561;
    group[Person("Andrew",18)] = 40562;
    for ( auto ii = group.begin() ; ii != group.end() ; ii++ )
        cout << ii->first.name 
        << " " << ii->first.age
        << " : " << ii->second
        << endl;
    return 0;
}

 

我們利用std::function為MyCompare()構建函數實例。初始化時,這個函數實例就會被分配那個指向MyCompare()的指針。因此,在對group進行申明時,需要構造函數指明函數實例。

另外,c++11增加了一個新的關鍵詞decltype,它可以直接獲取自定義哈希函數的類型,並把它作為參數傳送。因此,group的聲明可以如下修改。

map<Person, int, decltype(&MyCompare)> group(MyCompare);

 

3.2 方法2: 重載operator()的類
方法2就是利用重載operator()的類,將比較函數打包成可以直接調用的類。

#include <iostream>
#include <map>
#include <string>
using namespace std;

class Person{
public:
    string name;
    int age;

    Person(string n, int a){
        name = n;
        age = a;
    }
};

struct MyCompare{  //Function Object
    bool operator()(const Person &p1, const Person &p2) const{
        return (p1.age < p2.age) || (p1.age == p2.age && p1.name.length() < p2.name.length());
    }
};

int main(int argc, char* argv[]){
    map<Person, int, MyCompare> group;
    group[Person("Mark", 17)] = 40561;
    group[Person("Andrew",18)] = 40562;
    for ( auto ii = group.begin() ; ii != group.end() ; ii++ )
        cout << ii->first.name 
        << " " << ii->first.age
        << " : " << ii->second
        << endl;

    return 0;
}

 

值得注意的是,這時group的聲明不再需要將函數對象的引用傳入構造器里。因為map會追蹤類定義,當需要比較時,它可以動態地構造對象並傳遞數據。

 

3.3 方法3: less函數的模板定制
前面幾種方法,無論我們怎么定義,在聲明group的時候都需要指定第3個參數,有什么方法是需要指定的呢?當然有啦。

通過map的定義可知,第三個參數的默認值是less<key>。顯而易見,less<key>屬於模板類。那么,我們可以對它進行模板定制,如下所示。

#include <iostream>
#include <map>
#include <string>
using namespace std;

class Person{
public:
    string name;
    int age;

    Person(string n, int a){
        name = n;
        age = a;
    }
};

template <> //function-template-specialization
    struct less<Person>{
    public :
        bool operator()(const Person &p1, const Person &p2) const {
            return (p1.age < p2.age) || (p1.age == p2.age && p1.name.length() < p2.name.length());
        }
};

int main(int argc, char* argv[]){
    map<Person, int> group; //無需指定第三個參數啦
    group[Person("Mark", 17)] = 40561;
    group[Person("Andrew",18)] = 40562;
    for ( auto ii = group.begin() ; ii != group.end() ; ii++ )
        cout << ii->first.name 
        << " " << ii->first.age
        << " : " << ii->second
        << endl;

    return 0;
}

 

================ End

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM