在 C++ std::set 中如何利用不同类型的值进行搜索?

news2024/9/30 9:38:15

在 C++ 集合中如何利用不同类型的值进行搜索

  • 一、背景
  • 二、初衷
  • 三、is_transparent
  • 四、总结

一、背景

C++14 引入了一项引人注目的功能,为关联容器弥补了某些案例中长久以来的不足之处:即允许使用在语义上被视为键,但在技术上并非真正键的值对关联容器进行搜索。这意味着可以在关联容器中使用与存储元素不同类型的值进行查找。例如,可以使用员工的 ID 在一组包含 Employee 对象的集合中进行搜索,即使该 ID 并不构成 Employee 对象的组成部分。

在这里插入图片描述

二、初衷

这一功能在set的上下文中尤为重要。一些集合存储的对象包含其自身的键,换句话说,这些对象有一个可以视作键的子成员,例如 ID,而整个对象则被视为值。这在许多实际应用中十分常见,例如当希望存储一组具有唯一标识符的Employee信息时。

这些对象通常采用如下形式:

class Employee
{
public:
    explicit Employee(int id, std::string const& name) : id_(id), name_(name) {}
    int getId() const { return id_; }
    std::string getName() const { return name_; }

private:
    int id_;
    std::string name_;
};

Employee 类表示一个员工,其中包含两个数据成员:id_name_,分别用于表示员工的 ID 和姓名。

希望能够在 std::set 中存储多个员工。由于比较任意两个员工并决定其相对大小没有实质意义,而每名员工都有唯一的 ID,凭借该 ID 即可提供一个技术性顺序,从而按该顺序对员工在集合中进行排序。因此,选择使用 std::set 来存储员工,并根据其 ID 进行排列。

为实现这一目的,C++ 的std::set提供了自定义比较器的功能。比较器是一个函数对象,它定义了集合中元素的比较规则。

struct CompareId
{
    bool operator()(Employee const& employee1, Employee const& employee2) const {
        return employee1.getId() < employee2.getId();
    }
};

std::set<Employee, CompareId> employees;

定义了名为 CompareId 的比较器,明确了如何对两个 Employee 对象进行比较。该比较器利用 operator() 函数接收两个 Employee 对象为参数,并返回布尔值,指示第一个员工是否小于第二个员工。比较器通过 getId() 方法获取每个员工的 ID,并依据这些 ID 进行比较。

通过这样的方式,集合中的员工将根据 ID 进行合理排序。该功能自 C++98 以来便已存在。

然而,一旦开始使用此排序特性,通常会出现一个基本需求,即通过 ID 搜索员工。即希望能够利用一个 ID 值来查找集合中对应的员工。

你可能会想:“没问题!我可以添加几个比较函数来解决这个问题!”。

struct CompareId
{
    bool operator()(Employee const& employee1, Employee const& employee2) const
    {
        return employee1.getId() < employee2.getId();
    }
    bool operator()(int id, Employee const& employee) const
    {
        return id < employee.getId();
    }
    bool operator()(Employee const& employee, int id) const
    {
        return employee.getId() < id;
    }
};

添加了两个新的 operator() 函数,以便能够比较 int 类型的值和 Employee 对象。这样一来,程序员可以通过传入一个整数 ID(代表员工的 ID)与集合中的 Employee 对象进行比较。

然而,当调用 ID 的搜索时…

std::set<Employee, CompareId> employees = { Employee(1, "John"), Employee(2, "Bill") };
std::cout << employees.find(1)->getName() << '\
';

该代码并未成功编译。“怎么会这样?”,“这是为什么呢?”。答案在于 find 方法的原型:

iterator find( const Key& key );

实际上,find 方法仅接受与集合元素类型完全相同的键。因此,尽管比较是基于元素的 ID 部分,仍然必须传入一个 Employee 对象。

C++ 文档应该存在解决办法。事实证明并未有简单的解决方案。一些不太合适的选择:

  • 通过向 Employee 类添加一个仅接收引用的构造函数,以构造不完整的“空”员工,仅为进行比较,
  • 通过使用 std::map<int, Employee>,从而打破了整个设计,导致 ID 的重复存储。
  • 通过将 Employee 类中的 ID 移除,并将其作为键用于 std::map<int, Employee> 中,进而破坏有意义的设计以避免 ID 的重复。

就在绝望的修改整个程序时,C++14 的到来及时为其提供了救赎。

三、is_transparent

本质上,C++14 通过为 find 方法引入新重载,以及对 countlower_boundupper_boundequal_range 重载的增强,填补了这一空白。这些重载是模板化的,理论上可以接受任何能够与 Employee 进行比较的类型,包括其 ID。

为了实现这些重载,比较函数对象需要定义一个名为 is_transparent 的类型别名。此类型别名的具体值并不重要,唯一重要的是其被定义的状态:

struct CompareId
{
    using is_transparent = void; // 可使用 void,也可选择 int 或 struct CanSearchOnId;
    bool operator()(Employee const& employee1, Employee const& employee2) const
    {
        return employee1.getId() < employee2.getId();
    }
    bool operator()(int id, Employee const& employee) const
    {
        return id < employee.getId();
    }
    bool operator()(Employee const& employee, int id) const
    {
        return employee.getId() < id;
    }
};

using is_transparent = void; 语句表明该比较器支持透明比较。这一特性允许使用不同于集合元素类型的值进行比较。

因此,find 方法将按预期顺利执行。以下代码将编译通过并产生结果:

std::set<Employee, CompareId> employees = { Employee(1, "Lion"), Employee(2, "Long") };
std::cout << employees.find(1)->getName() << '\
';

这一功能以比通用 lambda 表达式等新特性更为低调但仍然极具价值的方式融入标准库,为我们在使用关联容器时提供了一个更优雅且更简洁的方法,以便利用不同类型的键进行搜索。

四、总结

is_transparent 是 C++14 中一项强大的创新,允许我们以与集合元素类型不同的值进行搜索。这一功能简化了代码设计,提升了开发效率,为我们在关联容器上执行操作提供了更大的灵活性。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2179293.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VS code Jupyter notebook 导入文件目录问题

VS code Jupyter notebook 导入文件目录问题 引言正文引言 这几天被 VS code 中 Jupyter Notebook 中的文件导入折磨的死去活来。这里特来说明一下放置于不同文件夹下的模块该如何被导入。 正文 首先,我们需要按下 Ctrl + , 键打开设置,然后搜索 notebook file root。在如…

window java17改成java 8

window上装了两个版本的Java&#xff0c;目前全局生效的是Java17&#xff0c;想切换成java8的全局。但是在修改环境变量的Path之后&#xff0c;java -version 还是java 17 但是自己的JAVA_HOME 和Path 都没配错啊… 怕是%JAVA_HOME%\bin\ 有问题&#xff0c;我还特意重写了bin…

【GEE学习第二期】GEE数据类型、语法

【GEE学习第二期】GEE数据类型、语法 GEE基本数据类型基本语法循环条件判断累加 可视化影像与波段影像集数据导出数值与绘图保存影像 参考 GEE基本数据类型 GEE 中使用的主要数据类型是栅格&#xff0c;涵盖从本地到全球范围的图像&#xff0c;可从数百个卫星和航空资源获得图…

网盘能否作为FTP替代产品?企业该如何进行FTP国产化替代?

近年来&#xff0c;信创的概念引入和高效实践落地让更多的行业企业自发性地进行国产化替代&#xff0c;目前信创国产化替代还多发生在操作系统和应用层面&#xff0c;软件工具等目前还在下一阶段规划&#xff0c;但很多企业未雨绸缪&#xff0c;已经在做调研和尝试。 FTP作为世…

数据结构:并查集

数据结构&#xff1a;并查集 并查集原理实现框架初始化合并查询获取成员路径压缩其它 总代码 并查集 在生活中&#xff0c;经常会出现分组问题。比如一个班级分为多个小组&#xff0c;打篮球分为两方等等。在同一个组中的所有成员&#xff0c;就构成一个集合。对这种一个群体分…

基于SSM的会员管理系统【附源码】

基于SSM的会员管理系统&#xff08;源码L文说明文档&#xff09; 目录 4 系统设计 4.1 系统概述 4.2 数据库设计原则 4.3 数据表 第五章 系统实现 5.1用户功能模块 5.2管理员功能模块 5.3前台首页功能模块 4 系统…

可视化是工业互联网的核心技术之一,都有哪些应用场景?

一、工业互联网是什么&#xff0c;发展的来胧去脉 工业互联网是指利用互联网技术和物联网技术&#xff0c;将工业生产中的各种设备、机器、传感器等进行互联互通&#xff0c;实现信息的实时采集、传输和分析&#xff0c;从而实现生产过程的智能化、自动化和高效化。 工业互联网…

微信网页 上传图片压缩

微信网页上传图片时的压缩问题可以通过多种方法解决。以下是一些有效的方案和相关API的使用说明。 主要解决方案 1. 使用Canvas进行自定义压缩: 对于需要适配多种设备和格式的情况,可以利用Canvas API进行图片重绘和压缩。通过获取图片信息、设置Canvas尺寸、绘制图片并…

地图资源下载工具(geodatatool)下载 亚洲 8 米 DEM数据

本数据集提供的 DEM 镶嵌图是由 DigitalGlobe 卫星的超高分辨率 (VHR) 沿轨和跨轨立体图像生成的。为了生成 DEM 镶嵌图块&#xff0c;超过 4000 个 DEM 条带与加权平均 镶嵌程序合并&#xff0c;以减少错误并消除接缝。镶嵌图块为 100 公里 x 100 公里&#xff0c;8 米处为 …

【easypoi 一对多导入解决方案】

easypoi 一对多导入解决方案 1.需求2.复现问题2.1校验时获取不到一对多中多的完整数据2.2控制台报错 Cannot add merged region B5:B7 to sheet because it overlaps with an existing merged region (B3:B5). 3.如何解决第二个问题处理&#xff1a; Cannot add merged region …

tr命令:替换文本中的字符

一、命令简介 ​tr​ 命令用于转换或删除文件中的字符。它可以从标准输入中读取数据&#xff0c;对数据进行字符替换、删除或压缩&#xff0c;并将结果输出到标准输出。 ‍ 二、命令参数 格式 tr [选项] [集合1] [集合2]选项和参数 ​ ​-c​​: 指定 集合 1 的补集。​ …

Vulhub zico 2靶机详解

项目地址 https://download.vulnhub.com/zico/zico2.ova实验过程 将下载好的靶机导入到VMware中&#xff0c;设置网络模式为NAT模式&#xff0c;然后开启靶机虚拟机 使用nmap进行主机发现&#xff0c;获取靶机IP地址 nmap 192.168.47.1-254根据对比可知Zico 2的一个ip地址为…

以太网交换安全:MAC地址表安全

一、MAC地址表安全 MAC地址表安全是网络安全中的一个重要方面&#xff0c;它涉及到网络设备的MAC地址表的管理和保护。以下是对MAC地址表安全的详细介绍&#xff1a; &#xff08;1&#xff09;基本概念 定义&#xff1a;MAC地址表是网络设备&#xff08;如交换机&#xff0…

群晖安装Audiobookshelf(有声书)

一、Audiobookshelf是什么&#xff1f; Audiobookshelf是一款自托管的有声读物和播客服务器&#xff0c;用于管理和播放您的有声读物。为用户提供便捷、个性化的音频书籍管理与播放体验 支持网页端、安卓端、IOS端三端同步,支持对有声书进行不同分类。 二、安装教程 通过群晖…

【C语言进阶】一次解决字符串输入问题——为什么输入这么容易奔溃?

文章一览 写在前面一、scanf、getchar与gets函数的爱恨情仇1.1 scanf函数1.1.1 %c输入单个字符2. %s 输入字符串1.1.3 %d输入数字 1.2 getchar函数1.3 gets函数 二、不同输入情况下的使用三、回顾C语言的输入函数总结 写在前面 在之前的文章中&#xff0c;笔者详细介绍了scanf函…

基于Springboot+Vue的课程教学平台的设计与实现系统(含源码数据库)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 这个系…

Arm Linux 交叉编译openssl 1.1.1

一、openssl 源码下载 OpenSSL的官方网站或源代码仓库下载最新或指定版本的OpenSSL源代码。 官网地址&#xff1a; https://openssl-library.org/source/index.html 旧版本下载&#xff1a; https://openssl-library.org/source/old/index.html 这里以 1.1.1 为例 国内git…

网线最短不能短于1米?

大家都知道网线最长不能长于100米&#xff0c;但你有没有听说过“网线最短不能短于1米”这个说法呢&#xff1f;也有的朋友说不能低于0.6米。 有的网友说“‌‌网线最短1米的说法是真的。‌ 短于1米的网线电阻几乎为零&#xff0c;设备可能无法识别&#xff0c;因此在实际应用中…

Android 安卓内存安全漏洞数量大幅下降的原因

谷歌决定使用内存安全的编程语言 Rust 向 Android 代码库中写入新代码&#xff0c;尽管旧代码&#xff08;用 C/C 编写&#xff09;没有被重写&#xff0c;但内存安全漏洞却大幅减少。 Android 代码库中每年发现的内存安全漏洞数量&#xff08;来源&#xff1a;谷歌&#xff09…

【前端开发入门】html快速入门

目录 引言html基础模板内容html文档流html 标签块级元素行内元素功能性元素标签嵌套 html编码习惯总结 引言 本系列教程旨在帮助一些零基础的玩家快速上手前端开发。基于我自学的经验会删减部分使用频率不高的内容&#xff0c;并不代表这部分内容不重要&#xff0c;只是对于初学…