导读:本期聚焦于盲改大师创作的《C++ std::ranges::contains快速查找键值 C++23标准库最简语法【干货】》,敬请观看详情。判断容器里是否存在某个元素,过去的标准写法是 find 之后和 end 做比较,代码容易写得啰嗦。C++23 在标准库算法头文件中加入了 std::ranges::contains,把这一判断压缩成一行。对于 map 或 unordered_map,还可以用管道运算符接上 std::views::keys 或 std::views::values,直接对键集合或值集合做包含判断。不过需要清楚,这个范围算法本质上是线性遍历,vector、list、array 这类普通容器用起来最合适;map 和 unordered_map 已经提供了 O(log n) 或 O(1) 的成员 contains,应当优先使用。本文通过可编译代码示例演示基础用法、关联容器键值查找、投影参数和兼容写法,并说明当前主流编译器的开启条件与常见报错。掌握了它,可以少写不少冗余的 end 比较。

C++23 标准把 std::ranges::contains 正式加入了范围算法库。它的作用非常直接:给定一个范围和一个值,返回一个布尔值表示该值是否出现在范围中。与 std::ranges::find 返回迭代器相比,contains 把“是否存在”这个最常见需求固化成了更短的表达方式,尤其适合只关心有没有、不关心位置的场景。

C++ std::ranges::contains快速查找键值 C++23标准库最简语法【干货】

这个接口在关联容器上配合视图使用时尤其顺手。过去如果想知道一个 std::map 里是否存在某个键,可以调用成员函数 contains;但如果想用统一的算法风格,也可以把键提取成一个惰性范围再调用 std::ranges::contains。下面分别从基础用法、键值查找、性能差异和编译条件几个角度展开。

一、std::ranges::contains 的基本用法与接口说明

最常用的 std::ranges::contains 重载形式可以近似理解为:template<ranges::input_range R, class T> constexpr bool contains(R&& r, const T& value);。它接受一个范围 r 和一个待查找的值 value,返回值为 bool。如果范围中至少有一个元素等于 value,结果就是 true,否则为 false。这个算法还提供了带投影和谓词的版本,允许在结构体里按某个字段查找。

对普通类数组容器来说,std::ranges::contains 等价于 std::ranges::find(r, value) != std::ranges::end(r),但它把两次调用合并成一次,既减少了代码量,也避免了手写 end 时可能出现的临时范围问题。下面的例子演示了对 std::vector<int> 做包含判断的基本写法。

#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> v{1, 2, 3, 4, 5};

    bool has3 = std::ranges::contains(v, 3);
    bool has10 = std::ranges::contains(v, 10);

    std::cout << std::boolalpha;
    std::cout << "contains 3: " << has3 << '\n';
    std::cout << "contains 10: " << has10 << '\n';
}

这段代码只需要包含 <algorithm> 和 <vector> 两个头文件。输出结果会显示 true 和 false。可以看到,调用站点不再出现 begin、end、== 这类辅助词汇,意图非常清晰。

需要注意的是,std::ranges::contains 与 std::ranges::find 一样,对普通范围执行线性扫描。也就是说,如果元素数量为 N,最坏情况下会检查所有 N 个元素。它并不会因为名字叫 contains 就自动获得二分查找或哈希查找的复杂度。因此它更适合元素数量不大、或者本来就必须遍历所有元素的场景。

二、在关联容器中查找键和值

对于 std::map、std::unordered_map 这类关联容器,C++20 已经增加了成员函数 contains,可以直接判断某个键是否存在,例如 m.contains(key)。而 C++23 的范围算法则提供了一种更统一的风格:通过 std::views::keys 提取键视图,再交给 std::ranges::contains 判断。这样写的好处是,不关心容器内部是红黑树还是哈希表,只要它支持键值对迭代,视图就能统一工作。

用 std::views::keys 时,管道表达式 m | std::views::keys 会产生一个惰性视图,其中每个元素都是原容器的键。它并不复制任何数据,所以创建视图的开销很小。类似地,std::views::values 可以提取所有值。下面这段代码对一个 std::unordered_map<std::string, int> 分别做了键判断和值判断。

#include <algorithm>
#include <ranges>
#include <unordered_map>
#include <iostream>
#include <string>

int main() {
    std::unordered_map<std::string, int> scores{
        {"alice", 90},
        {"bob", 85},
        {"carol", 92}
    };

    // 推荐:使用成员函数 contains,平均 O(1)
    bool hasAlice = scores.contains("alice");

    // 用 ranges::contains 配合 keys 视图,语法简洁但为线性扫描
    auto keys_view = scores | std::views::keys;
    bool hasBob = std::ranges::contains(keys_view, std::string{"bob"});

    // 检查值集合
    auto values_view = scores | std::views::values;
    bool hasScore85 = std::ranges::contains(values_view, 85);

    std::cout << std::boolalpha
              << hasAlice << ' '
              << hasBob << ' '
              << hasScore85 << '\n';
}

这里有一个很容易被忽视的性能差异。成员函数 unordered_map::contains 的平均时间复杂度是 O(1),最坏情况与哈希冲突有关;而 std::ranges::contains 对 keys_view 的遍历是线性的,时间复杂度和元素数量成正比。对于只有三个元素的示例来说没有差别,但如果 map 有几十万甚至上百万条记录,线性和 O(1) 的差距就会非常明显。

同理,std::map 的成员 contains 是 O(log n),而范围算法配 keys_view 仍然是 O(n)。因此在实际开发中,如果容器本身提供了 contains,并且查找键是主要目标,应该优先调用成员函数。只有当你需要统一处理多种容器、或者容器是自建类型且不支持成员 contains 时,再考虑用范围算法加视图的方案。

三、与传统 find 方案及其他 contains 成员对比

C++20 里已经有 std::ranges::find,它返回迭代器,判断是否存在还需要再和 std::ranges::end 做一次比较。C++23 的 std::ranges::contains 把这一判断直接封装成 bool,并且同样支持投影参数。例如有一个结构体 Item,里面包含 id 和 name,想知道容器里是否存在 id 为 10 的对象,可以写成 std::ranges::contains(items, 10, &Item::id),不再需要手写 lambda。

下面的代码把成员投影版本与传统 find 写法放在一起对比。注意投影参数 &Item::id 是一个成员指针,它告诉算法在比较前先取出每个元素的 id 字段。这样既避免了 == 运算符的重载,也让代码保持简洁。

#include <algorithm>
#include <vector>
#include <ranges>
#include <iostream>
#include <string>

struct Item {
    int id;
    std::string name;
};

int main() {
    std::vector<Item> items{{1, "pen"}, {2, "book"}, {10, "lamp"}};

    // 使用投影只比较 id 字段
    bool hasId10 = std::ranges::contains(items, 10, &Item::id);

    // 传统 find 写法
    auto it = std::ranges::find(items, 10, &Item::id);
    bool hasId10_old = it != std::ranges::end(items);

    std::cout << std::boolalpha
              << hasId10 << ' '
              << hasId10_old << '\n';
}

除了 std::ranges::contains,标准库中还有一些名字相似的成员函数。下表列出了不同查找方式在返回类型、典型复杂度和推荐场景上的差异。

查找方式返回类型典型复杂度推荐场景
std::ranges::containsboolO(n)普通范围、只需存在性判断
std::ranges::find迭代器O(n)需要位置或后续操作
std::map::containsboolO(log n)有序关联容器键查找
std::unordered_map::containsbool平均 O(1)无序关联容器键查找

从表中可以看出,std::ranges::contains 的优势在于通用性和简洁性,而不是绝对性能。如果数据规模很大,并且容器本身提供了更快的查找接口,应当优先使用成员函数。如果数据已经存在于 std::vector、std::list 或 std::array 中,又没有排序,那么 std::ranges::contains 就是最自然的选择。

另一个常见误区是,有人会把 std::ranges::contains 当作二分查找的替代品。二分查找要求范围有序,并且使用 std::ranges::binary_search 才是 O(log n)。如果范围已经排序,要频繁判断存在性,用 binary_search 比线性 contains 快得多;但如果每次查找都排序,那成本反而更高。因此选型时要同时考虑数据规模、查找频率和容器是否有序。

四、编译条件与常见错误处理

std::ranges::contains 是 C++23 的新特性,需要在较新的编译器中开启对应标准。GCC 13 以上、Clang 17 以上以及新版本的 MSVC 通常已经支持。命令行编译时加上 -std=c++23,CMake 工程可以设置 CMAKE_CXX_STANDARD 为 23;如果使用 MSVC,也可以使用 /std:c++latest。

可以通过特性测试宏来判断当前标准库是否提供了这个算法。宏名是 __cpp_lib_ranges_contains,只要它被定义并且值至少为 202207L,就表示 std::ranges::contains 可用。下面是一个条件编译示例,在不支持的新标准下回退到自定义实现。

#include <algorithm>
#include <vector>

#if defined(__cpp_lib_ranges_contains)
    // C++23 标准库直接支持
    bool check_contains(const std::vector<int>& v, int target) {
        return std::ranges::contains(v, target);
    }
#else
    // 兼容旧标准:手写一个简单版本
    template<typename Range, typename T>
    bool my_contains(const Range& range, const T& value) {
        return std::find(std::begin(range), std::end(range), value) != std::end(range);
    }

    bool check_contains(const std::vector<int>& v, int target) {
        return my_contains(v, target);
    }
#endif

int main() {
    std::vector<int> v{1, 2, 3, 4};
    bool ok = check_contains(v, 3);
    (void)ok;
}

初学者最容易遇到的编译错误是在 std::map 上直接调用 std::ranges::contains(m, key)。因为 std::map 的元素类型是 std::pair<const K, V>,而 key 通常是 K 类型,两者无法直接比较,编译器会报出一长串模板错误。正确做法是使用 m | std::views::keys 先提取键视图,或者直接调用 m.contains(key)。

另一个容易出错的地方是漏掉头文件。虽然 std::ranges::contains 在 <algorithm> 中,但 std::views::keys 和 std::views::values 需要包含 <ranges>。如果只包含 <algorithm> 却使用视图,编译器会提示 views 不存在。把这两个头文件都包含进去,基本就能避免大多数与 contains 相关的编译问题。

总体来说,std::ranges::contains 是 C++23 一个非常实用的小改进。它没有引入新的性能魔法,却让存在性判断的代码更短、更可读。对于还在使用旧标准的项目,也可以先写一个简单的模板函数来模拟同样的体验。掌握它的能力边界,在正确场景下使用,可以显著减少冗余代码。

std::ranges::containsC++23标准库键值查找修改时间:2026-09-29 23:36:07

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0929/63597.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。