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", )] = ;
group[Person("Andrew",)] = ;
for (auto ii = group.begin() ; ii != group.end() ; ii++)
cout << ii->first.name
<< " " << ii->first.age
<< " : " << ii->second
<< endl;
return ;
}

这里,我们需要注意的是,在重载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", )] = ;
group[Person("Andrew",)] = ;
for ( auto ii = group.begin() ; ii != group.end() ; ii++ )
cout << ii->first.name
<< " " << ii->first.age
<< " : " << ii->second
<< endl;
return ;
}

我们利用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", )] = ;
group[Person("Andrew",)] = ;
for ( auto ii = group.begin() ; ii != group.end() ; ii++ )
cout << ii->first.name
<< " " << ii->first.age
<< " : " << ii->second
<< endl; return ;
}

值得注意的是,这时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", )] = ;
group[Person("Andrew",)] = ;
for ( auto ii = group.begin() ; ii != group.end() ; ii++ )
cout << ii->first.name
<< " " << ii->first.age
<< " : " << ii->second
<< endl; return ;
}

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

map自定义键值类型的更多相关文章

  1. java map添加另一个map时候 键值对的类型要一致

    java map添加另一个map时候 键值对的类型要一致

  2. std::map使用结构体自定义键值

    使用STL中的map时候,有时候需要使用结构题自定义键值,比如想统计点的坐标出现的次数 struct Node{ int x,y; }; ...... map<Node,int>mp; m ...

  3. STL: unordered_map 自定义键值使用

    使用Windows下 RECT 类型做unordered_map 键值 1. Hash 函数 计算自定义类型的hash值. struct hash_RECT { size_t operator()(c ...

  4. Android下添加新的自定义键值和按键处理流程【转】

    本文转载自: Android下添加新的自定义键值和按键处理流程     说出来不怕大家笑话,我写这篇博客的原因在于前几天去一个小公司面试Android系统工程师,然后在面试的时候对方的技术总监问了我一 ...

  5. Android下添加新的自定义键值和按键处理流程

            Android下添加新的自定义键值和按键处理流程     说出来不怕大家笑话,我写这篇博客的原因在于前几天去一个小公司面试Android系统工程师,然后在面试的时候对方的技术总监问了我 ...

  6. Java中Map根据键值(key)或者值(value)进行排序实现

    我们都知道,java中的Map结构是key->value键值对存储的,而且根据Map的特性,同一个Map中 不存在两个Key相同的元素,而value不存在这个限制.换句话说,在同一个Map中Ke ...

  7. PhoneBean实体类的封装和map输出键值对的设置

    之前我们写好了bean类型.现在我们再看看这个需求中,map和reduce各自的流程. Map阶段: 字段切分以后保留如下字段:以第一行为例,就保留13726230503112  2481 24681 ...

  8. map集合键值对存储,键值不重复,值可以重复

    import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.Li ...

  9. FAQ:注册表_键值类型

    在注册表中,“键值项数据”可分为下面三种类型. 字符串值(REG_SZ) 该值一般用来作为文件描述和硬件标志,可以是字母.数字,也可以是汉字,但它是长度固定的文本字符串,最大长度不能超过255个字符. ...

随机推荐

  1. java如何压缩多个文件到压缩包,并下载到浏览器?

    java压缩多个文件到压缩包,并下载到浏览器   解决方法: 完整的方法如下,很简单,亲试有效,极力推荐. 我是以流作为文件,而不是file,循环把所有pdf文件压缩到pdf.zip压缩包中. 1.前 ...

  2. Qt编写自定义控件39-导航标签

    一.前言 在很多菜单导航界面中,当单击了二级菜单或者三级菜单以后,顶部会显示带箭头或者其他标识的导航标签,可以单击该标签快速切换到对应的界面,也作为指示当前处于哪一级菜单下的界面,主要在WEB中大肆流 ...

  3. python多进程——fork()

    简介    程序每次执行时,操作系统都会创建一个新进程来运行程序指令.进程中可调用os.fork,要求操作系统新建一个子进程.[Windowsc系统中,os模块没有os.fork函数]. 每个进程都有 ...

  4. SQLAlchemy相关文档

    目录 参考文档 一.执行原生SQL语句 1.实例一 2.实例二 2.实例三 二.ORM操作 1.创建数据库表 (1)创建单表 (2)创建多个表并包含FK.M2M关系 2.操作数据库表 (1)基于sco ...

  5. Flutter 踩坑之build函数返回了null

    今天遇到一个bug,内容都正常显示没问题,但是控制台里报错,如图: 翻译了下,说是函数不能返回空值,搜索了下,网上相同问题的是少写了个return,我检查了下也没发现少return的,后来突然发现if ...

  6. 图形学入门(3)——区域填充算法(region filling)

    继续图形学之旅,我们已经解决了如何画线和画圆的问题,接下来要解决的是,如何往一个区域内填充颜色?对一个像素填充颜色只需调用SetPixel之类的函数就行了,所以这个问题其实就是:如何找到一个区域内的所 ...

  7. 使用JavaScript实现量化策略并发执行

    本文代码和文章发在FMZ发明者比特币量化交易平台上: 使用JavaScript实现量化策略并发执行--封装Go函数 - 发明者量化 https://www.fmz.com/digest-topic/3 ...

  8. cisco 各类子网的划分

    C类地址子网划分 /25 子网掩码为128: 1位的取值为1,其他7位的取值为0(10000000): 块大小128: (256-128): 2个子网,每个子网最多包含126台主机: /26 子网掩码 ...

  9. U1. 广度优先搜索(BFS)和 广度优先搜索(DFS)

    广度优先搜索用栈(stack)来实现,整个过程可以想象成一个倒立的树形: 1.把根节点压入栈中. 2.每次从栈中弹出一个元素,搜索所有在它下一级的元素,把这些元素压入栈中.并把这个元素记为它下一级元素 ...

  10. python 脚本备份mssql数据库并删除数据库

    一.实现脚本 # -*- coding=utf-8 -*- import pyodbc from datetime import datetime import pymssql import os i ...