首页
/ JSON for Modern C++ 中 crbegin() 详解:basic_json 的 const 反向迭代起点

JSON for Modern C++ 中 crbegin() 详解:basic_json 的 const 反向迭代起点

2026-09-06 18:43:57作者:滕妙奇

JSON for Modern C++ 中,nlohmann::basic_json::crbegin() 是所有只读反向遍历操作的入口点。本文以官方 API 文档 crbegin.md 为主线,结合头文件实现与单元测试,讲清它的签名、返回值、异常安全、复杂度,以及它与 crend()rbegin()cbegin()/cend() 之间的协作关系,帮助你在处理 JSON 数组或对象时安全地进行逆向遍历。

1. 函数签名与语义

文档给出的声明为:

const_reverse_iterator crbegin() const noexcept;

其语义是:返回指向"反向起点"(reverse-beginning)的迭代器,也就是容器中的最后一个元素

  • 返回值:指向最后一个元素的 const_reverse_iterator(const 反向迭代器)。
  • 异常安全:no-throw guarantee,该成员函数永不抛出异常。
  • 时间复杂度:Constant(常数时间)。
  • 版本:自 1.0.0 起提供。

反向迭代器 rbegin/rend 位置示意

与普通的 begin()/end() 相反,crbegin() 返回的迭代器从尾部向前推进;由于 basic_jsonconst_reverse_iterator 本身只支持只读访问,通过 crbegin() 遍历容器时任何写入尝试(*it = ...)都无法通过编译,这正是 const 容器的预期行为。

2. 官方示例:获取数组最后一个元素

文档(crbegin.md)配套的完整示例位于 examples/crbegin.cpp,对应的真实输出保存在 crbegin.output

#include <iostream>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

int main()
{
    // create an array value
    json array = {1, 2, 3, 4, 5};

    // get an iterator to the reverse-beginning
    json::const_reverse_iterator it = array.crbegin();

    // serialize the element that the iterator points to
    std::cout << *it << '\n';
}

运行输出:

5

这个示例展示了 crbegin() 最基本的用法:对数组 {1, 2, 3, 4, 5}crbegin() 指向 5。对对象类型,crbegin() 则指向该对象内部的最后一个键值对(basic_json 对象默认按字典序存储,"最后一个"即排序后的末项)。

3. 底层实现:crbegin() 如何得到"最后一个元素"

在聚合头文件 single_include/nlohmann/json.hpp 中,实现只有三行:

const_reverse_iterator crbegin() const noexcept
{
    return const_reverse_iterator(cend());
}

即:crbegin() 并不直接定位"最后一个元素",而是把 cend()(指向末尾之后的 const 迭代器)包装进反向迭代器。这是 C++ 标准库反向迭代器的经典构造方式——std::reverse_iterator 持有指向"下一个正向迭代器"的基迭代器,其 operator* 通过 --base() 解引用,所以包装 cend() 后恰好解引用出最后一个元素。

配套的 crend()single_include/nlohmann/json.hpp)则是反向终点,即指向"第一个元素之前":

const_reverse_iterator crend() const noexcept
{
    return const_reverse_iterator(cbegin());
}

3.1 类型定义

迭代器类型在 single_include/nlohmann/json.hpp 附近声明,其中 const_reverse_iterator 的定义为:

using const_reverse_iterator = json_reverse_iterator<typename basic_json::const_iterator>;

const_iterator 本身是 iter_impl<const basic_json>。这说明 const 反向迭代器是对 const 正向迭代器的适配,整条链路都锁定在只读访问上。

3.2 反向迭代器适配器 json_reverse_iterator

真正承载 ++/--+/-[]key()value() 行为的类是 json_reverse_iterator.hpp 中的 detail::json_reverse_iterator<Base>,它继承自 std::reverse_iterator<Base>,并做了几件值得注意的事:

  • static_cast 把基类的自增/自减结果转回 json_reverse_iterator,保证 crbegin() 返回的迭代器在 ++/-- 后仍是可继续使用的同类迭代器(json_reverse_iterator.hpp);
  • 提供 key()value() 辅助方法,内部先执行 --this->base() 再取键/值(json_reverse_iterator.hpp)。这一点解释了反向迭代器为何必须先"回退一步"才能访问元素:它持有的基迭代器语义上是"当前元素的下一项"。

此外,源码注释声明该类满足 BidirectionalIteratorOutputIterator 概念要求(REQ-JSON-02,json_reverse_iterator.hpp);对于 const 基底(const_iterator),输出能力自然不生效,只保留双向移动与只读访问。

3.3 与 rbegin() 的关系

从源码结构看,const 版本的 rbegin()/rend()crbegin()/crend() 的转发:

const_reverse_iterator rbegin() const noexcept { return crbegin(); }   // L24250
const_reverse_iterator rend()   const noexcept { return crend();   }   // L24264

single_include/nlohmann/json.hpp。也就是说:对 const 对象,rbegin()crbegin() 完全等价;区别仅在于非 const 对象上 rbegin() 返回可写的 reverse_iterator,而 crbegin() 永远是 const 的。

4. 单元测试中的用法验证

仓库测试对 crbegin() 的行为有两类直接验证:

  • tests/src/unit-capacity.cpp 中,对多种 value_t 类型的 const JSON 值反复断言 std::distance(j_const.crbegin(), j_const.crend()) == j_const.size(),确认 const 反向区间 [crbegin(), crend()) 的长度恒等于 size()(该文件在 L223、L243、L265 等十余处重复此断言,覆盖 array、object、string、number 等类型);
  • tests/src/unit-iterators1.cpp"json + crbegin/crend" 小节中,先 json::const_reverse_iterator it = j.crbegin(); 取迭代器,再验证其解引用与步进行为。

这两个测试共同印证了文档中"返回最后一个元素、常数时间、永不抛异常"的规格描述。

5. 实战:逆向遍历数组与对象

基于以上语义,典型的逆向遍历写法如下(数组场景):

nlohmann::json array = {1, 2, 3, 4, 5};

// 反向打印:5 4 3 2 1
for (auto it = array.crbegin(); it != array.crend(); ++it) {
    std::cout << *it << ' ';
}

对对象,借助反向迭代器特有的 key()/value() 方法(实现见 json_reverse_iterator.hpp)可以按键的逆字典序访问:

nlohmann::json obj = {{"a", 1}, {"b", 2}, {"c", 3}};

for (auto it = obj.crbegin(); it != obj.crend(); ++it) {
    std::cout << it.key() << " -> " << it.value() << '\n';
}
// 输出(逆字典序):
// c -> 3
// b -> 2
// a -> 1

需要注意的边界情况:

  • 空容器:对空 JSON(null、空数组、空对象),crbegin() == crend(),循环体不执行;这与标准容器的反向区间语义一致;
  • 只读保证crbegin() 只能在 const 上下文中被调用(它本身就是 const 成员函数,且返回 const 迭代器)。若你需要在遍历中修改元素,应使用非 const 版本 rbegin()(见 rbegin.md)配合 reverse_iterator
  • items() 的取舍:若只需遍历对象键值对而不关心顺序方向,官方更推荐 items() 代理(见 items.md);crbegin() 的价值恰恰在于"需要逆序"的场景,例如输出最近入队的记录、构造 LRU 式展示等。

6. 与正向迭代器的对照小结

成员函数 返回类型 指向 文档
cbegin() const_iterator 第一个元素 cbegin.md
cend() const_iterator 末尾之后 cend.md
crbegin() const_reverse_iterator 最后一个元素 crbegin.md
crend() const_reverse_iterator 第一个元素之前 crend.md

crbegin() 的常数时间复杂度意味着它本身不做任何扫描——代价全部在随后的 ++/-- 步进中。实现层面它仅是一次"用 cend() 构造反向迭代器"的适配(single_include/nlohmann/json.hpp),因此可以放心用于热路径。

7. 适用前提与限制

  • 本文描述对应仓库当前源码(single_include/nlohmann/json.hpp 中标注版本 3.12.0 时代的聚合头文件),crbegin() 自 1.0.0 起存在,行为在主线版本中保持稳定;
  • crbegin() 只读;需要写入请用 rbegin() 返回的可写反向迭代器;
  • 对非结构化类型(number、string、boolean、null、binary),从源码结构看 iter_impl 会将其视作"单元素区间"处理,crbegin()crend() 分别指向该逻辑区间的两端,std::distance(crbegin(), crend()) 仍等于 size(),这与 unit-capacity.cpp 中的断言一致。

掌握 crbegin() 后,配合 rbeginrendcbegincend 四个接口,你就可以覆盖 JSON for Modern C++ 中全部的正/反向、const/非 const 遍历需求。

登录后查看全文
热门项目推荐
相关项目推荐