怎样为std::map的自定义key提供比较操作(一)

2023-11-13

  stl的关联容器(map,set)的key一般要求提供 < 比较操作。假设我们有一个结构SomeKey:

struct SomeKey
{
    int a, b;
};

  要想以SomeKey作为std::map的key,需要为这个结构提供operator < 比较操作,比如:

// 实现1
bool operator < (const SomeKey& left, const SomeKey& right)
{
    if (left.a < right.a) // 主key
    {
        return true;
    }
    else if (left.a == right.a && left.b < right.b) // 次key
    {
        return true;
    }
    else
    {
        return false;
    }
}

  或者:

// 实现2
bool operator < (const SomeKey& left, const SomeKey& right)
{
    if (left.a != right.a) // 主key
    {
        return left.a < right.a;
    }
    
    if (left.b != right.b) // 次key
    {
        return left.b < right.b;
    }
    
    return false;
}

  这两种实现方式是很常见的了,似乎也没什么好聊的。不过在项目中,我一次又一次地遇到错误的operator < 实现,着实让人吃惊!比如:

// 实现3(错误)
bool operator < (const SomeKey& left, const SomeKey& right)
{
    if (left.a < right.a) // 主key
    {
        return true;
    }
    
    if (left.b < right.b) // 次key
    {
        return true;
    }

    return false;
}

  或者

// 实现4(错误)
bool operator < (const SomeKey& left, const SomeKey& right)
{
    return (left.a < right.a || left.b < right.b);
}

  这样的实现将导致荒谬的结果!比如有两个SomeKey对象:x {10, 20} 和 y {5, 30},采用“实现3”或“实现4”来比较 x 和 y 的大小,结果取决于比较的时候谁在前面,也就是说,如果这样比较:x < y,结果为真;而这样:y < x,结果也为真!似乎作者并没有理解 “ < ” 比较操作的含义。这种错误的比较操作,会导致std::map表现出怪异的行为:

std::map<SomeKey, int> m;
m.insert({ { 10, 20 }, 1 });
m.insert({ { 5, 30 }, 2 });
auto it = m.find({ 5, 30 });
if (it == m.end())
{
	std::cout << "找不到{5, 30}" << std::endl;
}
else
{
	std::cout << "找到{5, 30}" << std::endl;
}

  上面的代码片段会输出“找不到{5, 30}”。在向map表insert键值对时,是以被插入值(后称目标值)和红黑树上的节点(后称节点值)比较:
在这里插入图片描述

  而在map表中find时,是以节点值和目标值进行比较:
在这里插入图片描述

  因为{10, 20} < {5, 30},所以在{10, 20}的右子树上查找,自然就找不到了。再向map表中插入一个节点,行为就更怪异了:

m.insert({ { 8, 25 }, 3 });
it = m.find({ 5, 30 });
if (it == m.end())
{
	std::cout << "找不到{5, 30}" << std::endl;
}
else
{
	std::cout << "找到{5, 30}" << std::endl;
}

  这次将输出“找到{5, 30}”。因为{8, 25} < {10, 20},继续{8, 25} < {5, 30},最终{8, 25}插入到map表的最左子节点,破坏了红黑树的平衡,右旋后,整棵树变成:
这里写图片描述

  然后在表中find {5, 30},首先找到第一个不小于{5, 30}的节点,因为第一个节点值{5, 30} 不小于目标值{5, 30},而目标值{5, 30}也不小于该节点值{5, 30},于是就找到了目标值。这种怪异行为导致的bug是比较隐秘的:你在表中找一个值,时而找得到,时而又找不到。一般的产品代码中,map表中插入的对象数量都不在少数,你讨厌对大量数据的插入进行测试,你不会认为你的operator <区区几行代码会有bug,你又不可能怀疑map有什么问题,于是你很可能最终会将之归结于灵异现象。


  在产品代码中,结构体可能会更复杂,类似的operator < 错误实现也不少见。

  本文以一个小学生都能理解的例子来聊聊正确的`operator <`实现应该是怎样的。我们来比较两个数字的大小(为便于讨论,两个数都是两位的十进制数):


  假如有:x = 36,y = 49;则 x < y 为真;
  假如有:x = 36,y = 29;则 x < y 不为真;
  假如有:x = 36,y = 39;则 x < y 为真;
  假如有:x = 36,y = 32;则 x < y 不为真;
  假如有:x = 36,y = 36;则 x < y 不为真;


  我们是怎么正确比较出各种情况下 x 和 y 的大小的?实在是很简单:首先比较高位数(十位数),谁的高位数小,谁就小;谁的高位数大,谁就大;高位数相等的话,那就以相同的方式再比较低位数(个位数)。为什么要先比较高位数,因为高位数的“权”大啊!


  类似的,比较SomeKey的时候,哪个字段先来,哪个字段就是主key,SomeKey中,a是主key,b是次key,即 a 对于SomeKey对象“大小”的贡献占主导地位,如果 a 能决出胜负来,b 就不用比了,只有 a 不能决出胜负时(相等),才继续比较 b,无论怎么比较,结果要是稳定的!


  “实现3”和“实现4”要这样修改一下才行:

bool operator < (const SomeKey& left, const SomeKey& right)
{
    if (left.a < right.a) // 主key
    {
        return true; // 主key小,就小
    }
    
    if (left.a > right.a) // 主key
    {
        return false; // 主key大,就大
    }
    
    return left.b < right.b; // 主key相等,再比较次key
}

  说起来,本文其实不值一提!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

怎样为std::map的自定义key提供比较操作(一) 的相关文章

随机推荐

  • ABAP DOI技术中I_OI_SPREADSHEET接口的使用

    前言部分 大家可以关注我的公众号 公众号里的排版更好 阅读更舒适 正文部分 在DOI技术中 I OI SPREADSHEET接口有很多对excel的操作方法 举个例子 CELL FORMAT方法 这个方法里面就有参数ALIGN 可以去覆盖e
  • antd pro(ProLayout) mix混合菜单不生效

    一 问题描述 antd pro的混合菜单模式 算是一种比较新的导航菜单模式 可以让顶部全局导航 侧边导航混合模式同时出现 满足一些特别的需求 ProLayout高级布局组件的API里有一个layout参数 可以设置layout的菜单模式 我
  • nodejs-处理http请求

    文章目录 前言 node 处理 get 请求 node 处理 post 请求 总结 前言 使用nodejs搭建后端代理服务 处理http请求 理解nodejs是如何处理get post请求的 node 处理 get 请求 使用 http 模
  • 时序预测

    时序预测 MATLAB实现SO CNN BiLSTM蛇群算法优化卷积双向长短期记忆神经网络时间序列预测 目录 时序预测 MATLAB实现SO CNN BiLSTM蛇群算法优化卷积双向长短期记忆神经网络时间序列预测 预测效果 基本介绍 程序设
  • uniapp实现逆解析地址(经纬度换具体地址)

    调用高德地图的sdk ifndef H5 this qqmapsdk reverseGeocoder get poi 1 poi options address format short policy 1 radius 3000 page
  • 验证网站列表,持续更新中...

    verificationacademy com verificationguide com chipverify com https www runoob com w3cnote verilog2 sdf html https www th
  • Altium Designer常用快捷键

    1 shift s 切换单层显示 2 q 英寸和毫米 尺寸切换 3 D R 进入布线规则设置 其中 Clearance 是设置最小安全线间距 覆铜时候间距 比较常用 4 CTRL 鼠标单击某个线 整个线的NET 网络 呈现高亮状态 5 小键
  • Android平台一对一音视频通话方案对比:WebRTC VS RTMP VS RTSP

    一对一音视频通话使用场景 一对一音视频通话都需要稳定 清晰和流畅 以确保良好的用户体验 常用的使用场景如下 社交应用 社交应用是一种常见的使用场景 用户可以通过音视频通话进行面对面的交流 在线教育 老师和学生可以通过音视频通话功能进行实时互
  • vue 封装一个滚动组件和使用自定义指令封装一个滚动触底触发回调指令(纵向滚动瀑布流)

    方式一 滚动组件 纵向滚动触发触底事件和触顶事件
  • 在传统公司干是一种什么体验(八)

    永远不要相信酒桌上说的话 表哥语录 表哥去了新公司之后 经常参加应酬 因为表哥比较实诚 来者不拒 喝酒特别容易喝多 但是表哥有个好处 喝多了不说话 而且第二天能记住昨晚的事 表哥在酒桌上已经收获了999 客户跟表哥的结拜 999 大BOSS
  • 关于Unity加载优化,你可能会遇到这些问题

    关键词 资源加载 卸载 实例化实例化 资源管理方法 一 资源加载 Q1 Shader 是独立打包的 如果我在开始游戏的时候加载一次 以后切换场景时就不用每次加载了吗 确切地说 要实现后续Shader不加载开销 需要满足以下两个条件 1 包含
  • Sophus安装踩坑

    装SLAM十四讲第二版提供的Sophus Eigen版本3 4 0 报错 home ch 下载 Sophus 13fb3288311485dc94e3226b69c9b59cd06ff94e test core test so2 cpp 9
  • JAVAfx11打包部署

    1 将默认打包工具删除 添加maven shade plugin依赖 如下
  • 有实力的人才能谈梦想

    我总是徘徊 在犹豫 觉得自己做不到 只是在苟延残喘摆了 所谓的目标也不可能实现 今天我发现我做到了 原来也不是那么遥不可及 是自己不够自信 不够淡定 有实力的人聊梦想 没理想的人就想想怎么混工作吧 有实力的人 自信 坚定的毅力 不怕失败 淡
  • Vue.directive指令(自定义指令)

    定义方式 html页面定义 Vue directive hello function el binding vnode el style color binding value 全局定义 Vue directive hello insert
  • 2.搭建Fabric区块链网络环境——前提条件和fabric的安装

    1 安装前提条件 这些前提条件的满足确保了你可以顺利地搭建和运行 Fabric 区块链网络 并进行链码的开发 部署和执行 安装 Docker 确保系统上已经安装了 Docker 并且 Docker 服务正在运行 Docker Fabric
  • [MySQL]MySQL内置函数

    MySQL MySQL内置函数 文章目录 MySQL MySQL内置函数 1 日期函数 2 字符串函数 3 数学函数 4 其他函数 1 日期函数 常用日期函数如下 函数名称 描述 current date 获取当前日期 current ti
  • Trick: QSplashScreen中设置其他控件,并控制其大小

    方案一 使用qss设置控件内容 无法自适应窗口大小 方案二 在代码中通过QFont等类 代码直接设置大小 大小可为变量 窗口跳转时出现控件闪动 大小变动 方案Final 在代码中使用setStyleSheet 设置控件 QFont font
  • css文本输入框的样式

    form control display block width 100 height 34px padding 6px 12px font size 14px line height 1 428571429 color 555555 ve
  • 怎样为std::map的自定义key提供比较操作(一)

    stl的关联容器 map set 的key一般要求提供 lt 比较操作 假设我们有一个结构SomeKey struct SomeKey int a b 要想以SomeKey作为std map的key 需要为这个结构提供operator lt