详细介绍如何使用rapidjson读取json文件
本文主要详细介绍如何使用rapidjson库来实现.json文件的读取,分为相关基础介绍、结合简单示例进行基础介绍、结合复杂示例进行详细的函数实现介绍等三部分。
一、相关基础
1、Json文件中的{}
和 []
在 JSON 文件中,{}
和 []
分别表示不同的数据结构:
(1) {}
表示对象(Object)
- 语义:
{}
表示键值对的集合。 - 结构:每个键是一个字符串,键与值之间用冒号(
:
)分隔,键值对之间用逗号(,
)分隔。 - 特点:
- 键是唯一的,值可以是任意类型(如字符串、数字、布尔值、数组、对象等)。
- 对象类似于 C++ 中的
std::map
或 Python 中的字典(dict
)。
示例:
{"name": "John", "age": 30, "married": true
}
- 解析:
name
是键,值为字符串"John"
。age
是键,值为整数30
。married
是键,值为布尔值true
。
(2) []
表示数组(Array)
- 语义:
[]
表示有序的值列表。 - 结构:值之间用逗号(
,
)分隔,值可以是任意类型(如字符串、数字、布尔值、数组、对象等)。 - 特点:
- 数组中的值没有键,只有索引(从 0 开始)。
- 数组类似于 C++ 中的
std::vector
或 Python 中的列表(list
)。
示例:
[10, 20, 30, 40]
- 解析:
- 数组包含 4 个整数值:
10
、20
、30
和40
。
- 数组包含 4 个整数值:
(3)综合示例
一个 JSON 文件可以同时包含对象和数组的嵌套:
{"person": {"name": "Alice","age": 25,"hobbies": ["reading", "cycling", "traveling"]},"scores": [85, 90, 78]
}
- 解析:
person
是一个对象,包含键值对:name
:"Alice"
(字符串)age
:25
(整数)hobbies
: 一个数组,包含 3 个字符串。
scores
是一个数组,包含 3 个整数。
(4)总结
{}
:用于表示对象(键值对的集合)。[]
:用于表示数组(有序值列表)。
2、常用函数
JSON 文件中常用的函数分类及详细列表如下所示,基于 RapidJSON 库进行了分类整理。
注:下面示例中的doc是用来存储解析后的JSON数据的主要对象,可通过rapidjson::Document doc
声明
(1) 文件处理相关
函数名 | 函数用途 | 使用示例 |
---|---|---|
ParseStream | 解析输入流中的 JSON 数据 | doc.ParseStream(isw); |
Parse | 解析字符串中的 JSON 数据 | doc.Parse(jsonStr.c_str()); |
HasParseError | 检查解析是否出错 | if (doc.HasParseError()) { /* 错误处理 */ } |
(2)解析与验证相关
函数名 | 函数用途 | 使用示例 |
---|---|---|
HasMember | 检查对象是否包含指定键 | if (doc.HasMember("key")) { /* 存在键 */ } |
IsObject | 检查 JSON 节点是否为对象 | if (doc["key"].IsObject()) { /* 是对象 */ } |
IsArray | 检查 JSON 节点是否为数组 | if (doc["key"].IsArray()) { /* 是数组 */ } |
IsString | 检查 JSON 节点是否为字符串 | if (doc["key"].IsString()) { /* 是字符串 */ } |
IsInt | 检查 JSON 节点是否为整数 | if (doc["key"].IsInt()) { /* 是整数 */ } |
IsDouble | 检查 JSON 节点是否为浮点数 | if (doc["key"].IsDouble()) { /* 是浮点数 */ } |
IsBool | 检查 JSON 节点是否为布尔值 | if (doc["key"].IsBool()) { /* 是布尔值 */ } |
IsNull | 检查 JSON 节点是否为空 | if (doc["key"].IsNull()) { /* 是空值 */ } |
函数名 | 函数用途 | 使用示例 |
---|---|---|
IsNumber | 检查 JSON 节点是否为数字(整数或浮点数) | if (doc["key"].IsNumber()) { /* 是数字 */ } |
IsUint | 检查 JSON 节点是否为无符号整数 | if (doc["key"].IsUint()) { /* 是无符号整数 */ } |
IsUint64 | 检查 JSON 节点是否为 64 位无符号整数 | if (doc["key"].IsUint64()) { /* 是64位无符号整数 */ } |
IsInt64 | 检查 JSON 节点是否为 64 位整数 | if (doc["key"].IsInt64()) { /* 是64位整数 */ } |
IsLosslessDouble | 检查浮点数是否能无损转换为整数 | if (doc["key"].IsLosslessDouble()) { /* 无损转换 */ } |
(3) 对象操作相关
函数名 | 函数用途 | 使用示例 |
---|---|---|
operator[] | 获取对象中指定键的值 | auto value = doc["key"]; |
MemberBegin | 获取对象的第一个键值对迭代器 | for (auto itr = obj.MemberBegin(); itr != obj.MemberEnd(); ++itr) { std::cout << itr->name.GetString(); } |
MemberEnd | 获取对象的最后一个键值对迭代器 | 同上 |
(4)数组操作相关
函数名 | 函数用途 | 使用示例 |
---|---|---|
operator[] | 获取数组中指定索引的值 | auto value = doc["key"][0]; |
Begin | 获取数组的第一个元素迭代器 | for (auto itr = array.Begin(); itr != array.End(); ++itr) { std::cout << itr->GetInt(); } |
End | 获取数组的最后一个元素迭代器 | 同上 |
Size | 获取数组大小 | size_t len = doc["key"].Size(); |
(5) 获取操作相关
函数名 | 函数用途 | 使用示例 |
---|---|---|
GetString | 获取字符串值 | std::string str = doc["key"].GetString(); |
GetInt | 获取整数值 | int val = doc["key"].GetInt(); |
GetDouble | 获取浮点数值(包括 float 类型) | double val = doc["key"].GetDouble(); |
GetBool | 获取布尔值 | bool flag = doc["key"].GetBool(); |
GetArray | 获取数组值(作为数组对象) | const auto& arr = doc["key"].GetArray(); |
GetObject | 获取对象值(作为对象) | const auto& obj = doc["key"].GetObject(); |
函数名 | 函数用途 | 使用示例 |
---|---|---|
GetUint | 获取无符号整数值 | unsigned int val = doc["key"].GetUint(); |
GetInt64 | 获取 64 位整数值 | int64_t val = doc["key"].GetInt64(); |
GetUint64 | 获取 64 位无符号整数值 | uint64_t val = doc["key"].GetUint64(); |
关于浮点数
-
基础的RapidJSON 中没有
GetFloat
函数。 -
浮点数统一通过
GetDouble
获取。即使是 JSON 文件中的浮点数值可以存储为单精度(float),也会被解释为双精度(double)。 -
若需要
float
类型的值,可以使用强制类型转换:float val = static_cast<float>(doc["key"].GetDouble());
-
即使在某些RapidJSON中,对
GetFloat
函数也进行了封装,比如我使用的这个版本的document.h文件中,进行了以下封装,所以也可以直接用GetFloat
函数。但其本质还是通过GetDouble
函数获取,然后进行类型转换后返回的。
[[_resources/详细介绍如何使用rapidjson读取json文件/bc2fcd9db0921f81d29d3018268dc70e_MD5.jpeg|Open: Pasted image 20241206092810.png]]
![[_resources/详细介绍如何使用rapidjson读取json文件/bc2fcd9db0921f81d29d3018268dc70e_MD5.jpeg]]
float GetFloat() const {return static_cast<float>(GetDouble());
}
综合示例
示例JSON文件如下:
{"Info": {"author": "JZX-MY","number": 7,},"regions": [{"name": "Area 1","points": [{"x": 1.0,"y": 2.0},{"x": 3.0,"y": 4.0}]},{"name": "Area 2","points": [{"x": 5.0,"y": 6.0},{"x": 7.0,"y": 8.0}]}]
}
读取该示例JSON文件的代码片段如下:
#include <iostream>
#include <rapidjson/document.h>
#include <rapidjson/istreamwrapper.h>
#include <fstream>int main() {// 读取 JSON 文件std::ifstream ifs("data.json");rapidjson::IStreamWrapper isw(ifs);// 解析 JSON 文件rapidjson::Document doc;doc.ParseStream(isw);// 检查是否存在错误if (doc.HasParseError()) {std::cerr << "解析失败!" << std::endl;return -1;}// 获取对象中的值std::string author = doc["Info"]["author"].GetString();int number = doc["Info"]["number"].GetInt();// 遍历数组const auto& regions = doc["regions"].GetArray();for (const auto& region : regions) {std::cout << "Region Name: " << region["name"].GetString() << std::endl;const auto& points = region["points"].GetArray();for (const auto& point : points) {std::cout << " Point: (" << point["x"].GetDouble() << ", " << point["y"].GetDouble() << ")" << std::endl;}}return 0;
}
二、结合简单示例进行详细的基础介绍
0. 示例
本部分内容以读取如下所示非常简单的test.json文件为例
{
"points": [-17, -15, 0],
"cost": 10,
"rate": 0.6,
}
对应的读取示例程序如下:
bool readJson(std::string& file_path, int& cost, double& occ_th, double& free_th) {std::string read_path = file_path + "/test.json";std::cout << "Read Json: " << read_path << std::endl;// 加载 JSON 文件std::ifstream ifs(read_path);if (!ifs.is_open()) {std::cout << " could not open " << read_path << std::endl;return false;}rapidjson::IStreamWrapper isw(ifs);rapidjson::Document doc;doc.ParseStream(isw);if (doc.HasParseError()) {std::cerr << "Error JSON file " << read_path << std::endl;return false;}// 解析 points 数组if (doc.HasMember("points") && doc["points"].IsArray() && doc["points"].Size() == 3) {points_.x() = doc["points"][0].GetDouble();points_.y() = doc["points"][1].GetDouble();std::cout << "points x: " << points_.x() << " points y: " << points_.y()<< " points th: " << doc["points"][2].GetDouble() << std::endl;} else {std::cerr << "Invalid points data in map_.json" << std::endl;return false;}// 读取 costif (doc.HasMember("cost") && doc["cost"].IsInt()) {cost = doc["cost"].GetInt();std::cout << "cost: " << cost << std::endl;} else {std::cerr << "The JSON does not contain a cost tag or it is invalid." << std::endl;return false;}// 读取 rateif (doc.HasMember("rate") && doc["rate"].IsDouble()) {occ_th = doc["rate"].GetDouble();std::cout << "rate: " << occ_th << std::endl;} else {std::cerr << "The JSON does not contain an rate tag or it is invalid." << std::endl;return false;}return true;
}
在上述代码中,函数 readJson
使用了 RapidJSON 读取和解析一个 JSON 文件。以下是分步骤的详细介绍,以及如何正确使用 RapidJSON 读取 JSON 文件并提取数据:
1. 准备工作
- 路径声明:std::string read_path = file_path + “/test.json”;
- 定义要解析的 JSON 文件路径。
示例如下:
std::string read_path = file_path + "/test.json";
std::cout << "Read Json: " << read_path << std::endl;
- 加载文件:通过
std::ifstream
打开文件流,确保文件可用。- 如果文件无法打开,返回错误。
示例如下:
std::ifstream ifs(read_path);
if (!ifs.is_open()) {std::cout << "test.json could not open " << read_path << std::endl;return false;
}
2. 创建 RapidJSON 的流包装器
RapidJSON 使用 IStreamWrapper
将标准的 C++ 输入流包装为可供 RapidJSON 解析的流。
rapidjson::IStreamWrapper isw(ifs);
- 文档对象:
rapidjson::Document doc
是用来存储解析后 JSON 数据的主要对象。 - 解析 JSON 流:通过
doc.ParseStream(isw)
解析 JSON 文件内容。
rapidjson::Document doc;
doc.ParseStream(isw);
if (doc.HasParseError()) {std::cerr << "Error JSON file " << read_path << std::endl;return false;
}
3. 验证和提取 JSON 数据
(1)验证 JSON 数据 【非必要】
- 可以使用
doc.HasParseError()
检查 JSON 文件的解析是否出错。 - 对每个 JSON 字段,使用
doc.HasMember("key")
检查该字段是否存在,并验证字段类型,例如IsArray()
、IsDouble()
等。
(2)解析 JSON 字段
{
"points": [-17, -15, 0],
"cost": 10,
"rate": 0.6,
}
数组字段解析示例——points
字段解析
已知points
是一个含有3个数字的数组字段,这里代码验证json文件中是否含有points
成员、它是否为数组,长度是否为 3,若验证成功,则提取数据,并保存至合适的变量中(示例中保存到了类内变量中)。
对于名为points
的数组字段,且数据类型为double,可以使用 doc[ "points" ][ 0 ].GetDouble();
来读取其第一个元素,以此类推
if (doc.HasMember("points") && doc["points"].IsArray() && doc["points"].Size() == 3) {points_.x() = doc["points"][0].GetDouble();points_.y() = doc["points"][1].GetDouble();std::cout << "points x: " << points_.x() << " points y: " << points_.y()<< " points th: " << doc["points"][2].GetDouble() << std::endl;
} else {std::cerr << "Invalid points data in map_.json" << std::endl;return false;
}
整数字段解析示例——cost
字段解析
cost
是一个整数字段,代码doc.HasMember(“cost”) 验证是否含有成员cost, 使用 IsInt()
验证其类型是否为int,并通过 GetInt()
获取值。
if (doc.HasMember("cost") && doc["cost"].IsInt()) {cost = doc["cost"].GetInt();std::cout << "cost: " << cost << std::endl;
} else {std::cerr << "The JSON does not contain a cost tag or it is invalid." << std::endl;return false;
}
浮点数字段解析示例——rate
字段解析
rate
是一个浮点数字段,代码使用 IsDouble()
验证类型,并通过 GetDouble()
获取值。
if (doc.HasMember("rate") && doc["rate"].IsDouble()) {occ_th = doc["rate"].GetDouble();std::cout << "rate: " << occ_th << std::endl;
} else {std::cerr << "The JSON does not contain an rate tag or it is invalid." << std::endl;return false;
}
4. 返回解析结果
在所有字段都成功解析后,函数返回 true
,否则返回 false
。
5. 总结 RapidJSON 的使用
- 文件读取:通过
std::ifstream
和rapidjson::IStreamWrapper
将文件内容传递给 RapidJSON。 - 文档解析:通过
rapidjson::Document
和ParseStream
将 JSON 数据加载到内存中。 - 字段验证:使用
HasMember
和字段类型检查确保数据完整性。 - 字段提取:通过
Get<Type>()
函数提取具体数据。
这种方法适用于处理结构明确的 JSON 文件,提供高效且安全的解析能力。
三、结合复杂示例进行详细的函数实现介绍
本部分以如下所示的示例为例子展开介绍
{"Info": {"author": "JZX_MY","version": "v1.0.96","number": 66,"position": [-9, 60.0, 10.0],"rate": 0.99},"scale": 77,"regions": [{"index": 0,"name": "free_regions","points": [{"x": -2.03,"y": 2.25},{"x": 1.39,"y": 5.82},{"x": 7.47,"y": 2.35},{"x": 5.50,"y": -1.36}]},{"index": 1,"name": "occupy_regions","points": [{"x": -2.03,"y": 27.25},{"x": 10.39,"y": 5.82},{"x": 78.47,"y": 2.35}]}],"param": {"max_threshold": 0.05,"max_num" : 10240,"success" : true},"robot": {"width": 1510,"length": 5180,"radius": 3420},"other": []
}
1、编写通用的基本读取框架
根据第二部分的介绍,我们先编写读取一个JSON所需的一些基本框架,如下所示,
/*** 使用rapidJSON解析JSON文件的函数* @param directory_path 文件所在的目录路径* @param file_name 文件名*/
bool readJSON(const std::string& directory_path, const std::string& file_name) {// (1)拼接完整的文件路径std::string full_file_path = directory_path + "/" + file_name;std::cout << "Read Json file: " << full_file_path << std::endl;// (2)打开JSON文件流std::ifstream ifs(full_file_path);if (!ifs.is_open()) {// 如果文件无法打开,输出错误信息并返回std::cerr << "could not open: " << full_file_path << std::endl;return false;}// (3)使用rapidJSON的IStreamWrapper包装文件流,方便解析rapidjson::IStreamWrapper isw(ifs);// (4)定义rapidJSON的Document对象,用于存储解析后的JSON数据rapidjson::Document doc;doc.ParseStream(isw);// (5)检查JSON文件解析是否成功if (doc.HasParseError()) {// 如果解析失败,输出错误信息并返回std::cerr << "Error JSON file!" << std::endl;return false;}// ------------------------开始读取------------------------------// 开始逐个解析读取JSON字段 [后面的步骤补充]..................// ------------------------读取完成------------------------------// 关闭文件流ifs.close();return true;
}
其详细介绍如下:
(1)为了方便对多个类似结构的文件进行读取,下面的示例采用了将文件所在路径directory_path和文件名file_name以形参传入的方式,将其拼接后得到完整的要读取的JSON文件的路径full_file_path
// (1)拼接完整的文件路径
std::string full_file_path = directory_path + "/" + file_name;
std::cout << "Read Json file: " << full_file_path << std::endl;
(2)使用std::ifstream(输入文件流)创建一个文件流对象ifs。传入full_file_path作为参数,表示要打开的文件路径。std::ifstream会尝试打开指定路径的文件,并准备从文件中读取数据。【注:文件路径需要是完整路径,只有当路径正确且文件存在时,文件才能成功打开,没有指定模式时,默认以只读模式打开文件(std::ios::in)】
// (2)打开JSON文件流
std::ifstream ifs(full_file_path);
if (!ifs.is_open()) {// 如果文件无法打开,输出错误信息并返回std::cerr << "could not open: " << full_file_path << std::endl;return false;
}
(3)使用RapidJSON 的IStreamWrapper
将标准的 C++ 输入流对象ifs包装为可供 RapidJSON 解析的流isw。
// (3)使用rapidJSON的IStreamWrapper包装文件流,方便解析
rapidjson::IStreamWrapper isw(ifs);
(4)定义rapidJSON的Document对象doc,用于存储解析后的JSON数据文档对象,然后通过 doc.ParseStream(isw)
解析 JSON 文件内容。
// (4)定义rapidJSON的Document对象,用于存储解析后的JSON数据
rapidjson::Document doc;
doc.ParseStream(isw);
(5)检查JSON文件解析是否成功
// (5)检查JSON文件解析是否成功
if (doc.HasParseError()) {// 如果解析失败,输出错误信息并返回std::cerr << "Error JSON file!" << std::endl;return false;
}
(6)若解析成功,则开始根据JSON文件的结构内容,依次进行读取【在下一节中介绍】
(7)成功读取完成后,关闭C++文件流ifs,并返回true
到这里读取一个JSON文件的常用框架就介绍完了,下面根据示例JSON文件的结构补充读取部分
2、在通用框架的基础上补充实际要读取的内容
(1)示例中的Info部分
首先回顾第一部分中的内容:{}
是对象,表示键值对的集合。每个键是一个字符串,键与值之间用冒号(:
)分隔,键值对之间用逗号(,
)分隔。键是唯一的,值可以是任意类型(如字符串、数字、布尔值、数组、对象等)。[]
是数组,表示有序的值列表。值之间用逗号(,
)分隔,值可以是任意类型(如字符串、数字、布尔值、数组、对象等)。 数组中的值没有键,只有索引(从 0 开始)。对象和数组可以互相嵌套,产生一些比较复杂的结构
本部分内容的结构相对简单,Info作为总的JSON文件对象的一个键值对中的键,值是一个对象,该对象中又包含了author、version、number、position、rate这五个键值对,除了position之外,其他键值对的值都是单个数据,position的值又是一个数组,数组中每个元素也都是普通的单个数据,没有再嵌套下去。
{"Info": {"author": "JZX_MY","version": "v1.0.96","number": 66,"position": [-9, 60.0, 10.0],"rate": 0.99}
}
我们先来看Info对象的第一个键值对"author": “JZX_MY”,常见的读取方式有以下三种
第一种:通过最简化的直接链式访问
可以直接通过链式访问来读取,如下所示,根据第一部分中常用函数的介绍,读取字符串类型用GetString()函数即可
std::string author = doc["Info"]["author"].GetString();
std::cout << "Author: " << author << std::endl;
在确定 JSON 的结构是固定的,并且不会缺少任何字段的情况下,可以直接使用类似于 doc["Info"]["author"]
的链式访问方式,代码更简洁。缺点是,如果 Info 不存在、不是对象,或者 author 不存在、不是字符串,则会导致程序崩溃。
第二种:通过分层安全访问
这种方式的核心是进行逐步检查,确保每一步的操作都符合预期的条件,然后再执行具体的逻辑。不会因为 JSON 结构问题导致程序崩溃。 能够在异常情况(例如字段缺失或类型不匹配)时,自定义处理逻辑(如打印错误信息)。但代码更长,显得繁琐。如下所示:
// 检查 JSON 对象是否包含名为 "Info" 的成员,并且该成员是一个对象
if (doc.HasMember("Info") && doc["Info"].IsObject()) {// 获取 "Info" 成员,并存储为一个常量引用const rapidjson::Value& info = doc["Info"];// 检查 "Info" 对象是否包含名为 "author" 的成员,并且该成员是一个字符串if (info.HasMember("author") && info["author"].IsString()) {// 获取 "author" 成员的字符串值,并存储到变量 author 中std::string author = info["author"].GetString();// 输出 "author" 的值到控制台std::cout << "Author: " << author << std::endl;}
}
第三种:通过链式安全访问
可以将前两种结合一下,先检查,然后再通过链式访问,省去了临时中间变量’const rapidjson::Value& info = doc[“Info”];’
// 检查 JSON 对象是否包含名为 "Info" 的成员,并且该成员是一个对象
if (doc.HasMember("Info") && doc["Info"].IsObject()) {// 直接链式访问检查 "Info" 对象中的 "author" 成员if (doc["Info"].HasMember("author") && doc["Info"]["author"].IsString()) {// 直接链式获取 "author" 的字符串值std::string author = doc["Info"]["author"].GetString();// 输出 "author" 的值到控制台std::cout << "Author: " << author << std::endl;}
}
该方式虽然看起来稍微简洁了一些,但是 每次链式访问都会重新解析路径,因此可能会稍微影响性能,尤其在大规模或复杂 JSON 时。使用临时中间变量(如 info
)会在逻辑上缓存某个子节点的引用,从而提高性能。如果后续多次访问 "Info"
的内容,建议保留 const rapidjson::Value& info
方式以减少重复解析。【即第二种方式】
综合来看,第二种分层安全访问的方式是最值得推荐的,version、number、rate项与author项类似(换成对应类型的函数即可),这里就不展开介绍了,我们再来看一下position项,给出带检查与不带检查的写法示例
第一种:通过最简化的直接链式访问
如果完全确定数据结构正确,并希望最简化代码,可以直接链式访问:
float x = doc["Info"]["position"][0].GetFloat();
float y = doc["Info"]["position"][1].GetFloat();
float z = doc["Info"]["position"][2].GetFloat();
std::cout << "Position: (" << x << ", " << y << ", " << z << ")" << std::endl;
缺点:
- 缺乏安全性检查,一旦 JSON 数据结构有误(例如
position
缺失、不是数组或者长度不足),会导致程序崩溃。
第二种:通过分层安全访问
这种方法逐层检查 JSON 的结构,确保安全性:
if (doc.HasMember("Info") && doc["Info"].IsObject()) {const rapidjson::Value& info = doc["Info"]; // 获取 "Info" 对象if (info.HasMember("position") && info["position"].IsArray()) {const rapidjson::Value& position = info["position"]; // 获取 "position" 数组// 检查数组长度并逐个读取元素if (position.Size() == 3) { // 假设数组有3个元素float x = position[0].GetFloat(); // 获取第一个元素float y = position[1].GetFloat(); // 获取第二个元素float z = position[2].GetFloat(); // 获取第三个元素// 输出读取结果std::cout << "Position: (" << x << ", " << y << ", " << z << ")" << std::endl;} else {std::cerr << "Position array size is not 3!" << std::endl;}} else {std::cerr << "Position does not exist or is not an array!" << std::endl;}
}
优点:
- 每一步都进行了显式检查,适用于不确定 JSON 数据结构是否完全符合预期的情况。
- 对数组的操作更清晰,容易扩展到更复杂的逻辑。
带检查的Info部分读取示例(仅打印未存储):
// 解析Info部分
if (document.HasMember("Info")) { // 检查是否包含"Info"字段const rapidjson::Value& info = doc["Info"];if (info.IsObject()) { // 确保"Info"是一个对象std::cout << "Info 部分解析结果:" << std::endl;if (info.HasMember("author") && info["author"].IsString()) {std::cout << "作者: " << info["author"].GetString() << std::endl; // 输出作者名称}if (info.HasMember("version") && info["version"].IsString()) {std::cout << "版本: " << info["version"].GetString() << std::endl; // 输出版本号}if (info.HasMember("number") && info["number"].IsInt()) {std::cout << "编号: " << info["number"].GetInt() << std::endl; // 输出编号}if (info.HasMember("position") && info["position"].IsArray()) {std::cout << "位置: ";for (auto& pos : info["position"].GetArray()) {std::cout << pos.GetFloat() << " "; // 输出位置坐标}std::cout << std::endl;}if (info.HasMember("rate") && info["rate"].IsDouble()) {std::cout << "速率: " << info["rate"].GetDouble() << std::endl; // 输出速率}}
}
(2)示例中的scale部分
本部分结构很简单,scale作为总的JSON文件对象的一个键值对中的键,值是一个普通的单个数据,没有嵌套。
{"scale": 77
}
直接访问:
int scale = doc["scale"].GetInt();
std::cout << "Scale: " << scale << std::endl;
安全的访问:
if (doc.HasMember("scale") && doc["scale"].IsInt()) {int scale = doc["scale"].GetInt(); // 读取 "scale" 的值std::cout << "Scale: " << scale << std::endl; // 输出结果
} else {std::cerr << "Scale is not present or not an integer!" << std::endl;
}
(3)示例中的regions部分
本部分结构相对复杂一点,regions作为总的JSON文件对象的一个键值对中的键,值是一个数组,数组中每个成员又是一个对象,该对象中含有index、name、points三个成员,其中points的值又是一个数组,数组中每个成员又是一个对象,该对象包含x和y两个键值对,值为普通变量,嵌套结束。
{"regions": [{"index": 0,"name": "free_regions","points": [{"x": -2.03,"y": 2.25},{"x": 1.39,"y": 5.82},{"x": 7.47,"y": 2.35},{"x": 5.50,"y": -1.36}]},{"index": 1,"name": "occupy_regions","points": [{"x": -2.03,"y": 27.25},{"x": 10.39,"y": 5.82},{"x": 78.47,"y": 2.35}]}]
}
第一种:最简洁的无检查写法
// 遍历regions数组中每个region
for (const auto& region : doc["regions"].GetArray()) {std::cout << "Region index: " << region["index"].GetInt() << std::endl;std::cout << "Region name: " << region["name"].GetString() << std::endl;// 遍历points数组中每个pointfor (const auto& point : region["points"].GetArray()) {std::cout << " Point: (x: " << point["x"].GetDouble()<< ", y: " << point["y"].GetDouble() << ")" << std::endl;}
}
第二种:逐层检查的最安全的写法
if (doc.HasMember("regions") && doc["regions"].IsArray()) {const rapidjson::Value& regions = doc["regions"];for (rapidjson::SizeType i = 0; i < regions.Size(); ++i) {const rapidjson::Value& region = regions[i];if (region.HasMember("index") && region["index"].IsInt()) {int index = region["index"].GetInt();std::cout << "Region index: " << index << std::endl;}if (region.HasMember("name") && region["name"].IsString()) {std::string name = region["name"].GetString();std::cout << "Region name: " << name << std::endl;}if (region.HasMember("points") && region["points"].IsArray()) {const rapidjson::Value& points = region["points"];for (rapidjson::SizeType j = 0; j < points.Size(); ++j) {const rapidjson::Value& point = points[j];if (point.HasMember("x") && point["x"].IsDouble() &&point.HasMember("y") && point["y"].IsDouble()) {double x = point["x"].GetDouble();double y = point["y"].GetDouble();std::cout << " Point: (x: " << x << ", y: " << y << ")" << std::endl;}}}}
}
第三种:仅针对顶层结构检查的的写法
if (doc.HasMember("regions") && doc["regions"].IsArray()) {for (const auto& region : doc["regions"].GetArray()) {int index = region["index"].GetInt();std::cout << "Region index: " << index << std::endl;std::string name = region["name"].GetString();std::cout << "Region name: " << name << std::endl;for (const auto& point : region["points"].GetArray()) {double x = point["x"].GetDouble();double y = point["y"].GetDouble();std::cout << " Point: (x: " << x << ", y: " << y << ")" << std::endl;}}
}
(4)示例中的param部分
{"param": {"max_threshold": 0.05,"max_num" : 10240,"success" : true}
}
第一种:无检查写法
const rapidjson::Value& param = doc["param"];
double max_threshold = param["max_threshold"].GetDouble();
int max_num = param["max_num"].GetInt();
bool success = param["success"].GetBool();std::cout << "Max Threshold: " << max_threshold << std::endl;
std::cout << "Max Num: " << max_num << std::endl;
std::cout << "Success: " << (success ? "true" : "false") << std::endl;
或
double max_threshold = doc["param"]["max_threshold"].GetDouble();
int max_num = doc["param"]["max_num"].GetInt();
bool success = doc["param"]["success"].GetBool();std::cout << "Max Threshold: " << max_threshold << std::endl;
std::cout << "Max Num: " << max_num << std::endl;
std::cout << "Success: " << (success ? "true" : "false") << std::endl;
第二种:带检查写法
if (doc.HasMember("param") && doc["param"].IsObject()) {const rapidjson::Value& param = doc["param"];if (param.HasMember("max_threshold") && param["max_threshold"].IsDouble()) {double max_threshold = param["max_threshold"].GetDouble();std::cout << "Max Threshold: " << max_threshold << std::endl;}if (param.HasMember("max_num") && param["max_num"].IsInt()) {int max_num = param["max_num"].GetInt();std::cout << "Max Num: " << max_num << std::endl;}if (param.HasMember("success") && param["success"].IsBool()) {bool success = param["success"].GetBool();std::cout << "Success: " << (success ? "true" : "false") << std::endl;}
}
(5)示例中的robot部分
{"robot": {"width": 1510,"length": 5180,"radius": 3420}
}
第一种:无检查写法
const rapidjson::Value& robot = doc["robot"];
int width = robot["width"].GetInt();
int length = robot["length"].GetInt();
int radius = robot["radius"].GetInt();std::cout << "Width: " << width << std::endl;
std::cout << "Length: " << length << std::endl;
std::cout << "Radius: " << radius << std::endl;
或
int width = doc["robot"]["width"].GetInt();
int length = doc["robot"]["length"].GetInt();
int radius = doc["robot"]["radius"].GetInt();std::cout << "Width: " << width << std::endl;
std::cout << "Length: " << length << std::endl;
std::cout << "Radius: " << radius << std::endl;
第二种:带检查写法
if (doc.HasMember("robot") && doc["robot"].IsObject()) {const rapidjson::Value& robot = doc["robot"];if (robot.HasMember("width") && robot["width"].IsInt()) {int width = robot["width"].GetInt();std::cout << "Width: " << width << std::endl;}if (robot.HasMember("length") && robot["length"].IsInt()) {int length = robot["length"].GetInt();std::cout << "Length: " << length << std::endl;}if (robot.HasMember("radius") && robot["radius"].IsInt()) {int radius = robot["radius"].GetInt();std::cout << "Radius: " << radius << std::endl;}
}
(6)示例中的other部分
{"other": []
}
第一种:仅检查数组是否为空
const rapidjson::Value& other = doc["other"];if (other.Empty()) {std::cout << "The 'other' array is empty." << std::endl;
} else {for (rapidjson::SizeType i = 0; i < other.Size(); ++i) {std::cout << "Element " << i << ": " << other[i].GetString() << std::endl;}
}
第二种:逐层检查写法
if (doc.HasMember("other") && doc["other"].IsArray()) {const rapidjson::Value& other = doc["other"];// 检查数组是否为空if (other.Empty()) {std::cout << "The 'other' array is empty." << std::endl;} else {// 遍历数组元素(如果有的话)for (rapidjson::SizeType i = 0; i < other.Size(); ++i) {std::cout << "Element " << i << ": " << other[i].GetString() << std::endl;}}
}
3、完整的程序示例 & 运行
(1)将以下内容存储为main.cpp
#include <iostream>
#include <fstream>
#include <string>
#include "rapidjson/document.h"
#include "rapidjson/istreamwrapper.h"/*** 使用rapidJSON解析JSON文件的函数* @param directory_path 文件所在的目录路径* @param file_name 文件名*/
void parseJSON(const std::string& directory_path, const std::string& file_name) {// 拼接完整的文件路径,判断 directory_path 是否为空,std::string full_file_path = directory_path.empty() ? file_name //若为空,说明要读取的json文件与当前可执行文件在同一目录下,则直接将file_name作为路径: (directory_path.back() == '/' // 判断 directory_path 的最后是否有 '/',若有则直接拼接 file_name,否则加上 '/'? directory_path + file_name : directory_path + "/" + file_name);// 打开JSON文件流std::ifstream ifs(full_file_path);if (!ifs.is_open()) {// 如果文件无法打开,输出错误信息并返回std::cerr << "无法打开文件: " << full_file_path << std::endl;return;}// 使用rapidJSON的IStreamWrapper包装文件流,方便解析rapidjson::IStreamWrapper isw(ifs);// 定义rapidJSON的Document对象,用于存储解析后的JSON数据rapidjson::Document document;document.ParseStream(isw);// 检查JSON文件解析是否成功if (document.HasParseError()) {// 如果解析失败,输出错误信息并返回std::cerr << "JSON解析错误!" << std::endl;return;}// 开始逐个解析JSON字段// 解析"Info"部分if (document.HasMember("Info")) { // 检查是否存在"Info"字段const rapidjson::Value& info = document["Info"];if (info.IsObject()) { // 确保"Info"是一个对象类型std::cout << "Info 部分解析结果:" << std::endl;// 读取作者信息if (info.HasMember("author") && info["author"].IsString()) {std::cout << "作者: " << info["author"].GetString() << std::endl; // 输出作者名称}// 读取版本号if (info.HasMember("version") && info["version"].IsString()) {std::cout << "版本: " << info["version"].GetString() << std::endl; // 输出版本号}// 读取编号if (info.HasMember("number") && info["number"].IsInt()) {std::cout << "编号: " << info["number"].GetInt() << std::endl; // 输出编号}// 读取位置数组if (info.HasMember("position") && info["position"].IsArray()) {std::cout << "位置: ";for (auto& pos : info["position"].GetArray()) {std::cout << pos.GetFloat() << " "; // 遍历并输出位置坐标}std::cout << std::endl;}// 读取速率if (info.HasMember("rate") && info["rate"].IsDouble()) {std::cout << "速率: " << info["rate"].GetDouble() << std::endl; // 输出速率}}}// 解析"scale"部分if (document.HasMember("scale") && document["scale"].IsInt()) {std::cout << "缩放比例: " << document["scale"].GetInt() << std::endl; // 输出缩放比例}// 解析"regions"部分if (document.HasMember("regions") && document["regions"].IsArray()) {std::cout << "区域解析:" << std::endl;const rapidjson::Value& regions = document["regions"];for (const auto& region : regions.GetArray()) { // 遍历区域数组// 输出区域索引if (region.HasMember("index") && region["index"].IsInt()) {std::cout << "区域索引: " << region["index"].GetInt() << std::endl;}// 输出区域名称if (region.HasMember("name") && region["name"].IsString()) {std::cout << "区域名称: " << region["name"].GetString() << std::endl;}// 输出区域点坐标if (region.HasMember("points") && region["points"].IsArray()) {std::cout << "区域点坐标:" << std::endl;for (const auto& point : region["points"].GetArray()) {if (point.HasMember("x") && point["x"].IsDouble() &&point.HasMember("y") && point["y"].IsDouble()) {std::cout << "x: " << point["x"].GetDouble() << ", y: " << point["y"].GetDouble() << std::endl;}}}}}// 解析"param"部分if (document.HasMember("param") && document["param"].IsObject()) {std::cout << "参数解析:" << std::endl;const rapidjson::Value& param = document["param"];// 输出最大阈值if (param.HasMember("max_threshold") && param["max_threshold"].IsDouble()) {std::cout << "最大阈值: " << param["max_threshold"].GetDouble() << std::endl;}// 输出最大数量if (param.HasMember("max_num") && param["max_num"].IsInt()) {std::cout << "最大数量: " << param["max_num"].GetInt() << std::endl;}// 输出成功状态if (param.HasMember("success") && param["success"].IsBool()) {std::cout << "是否成功: " << (param["success"].GetBool() ? "是" : "否") << std::endl;}}// 解析"robot"部分if (document.HasMember("robot") && document["robot"].IsObject()) {std::cout << "机器人参数:" << std::endl;const rapidjson::Value& robot = document["robot"];// 输出机器人宽度if (robot.HasMember("width") && robot["width"].IsInt()) {std::cout << "宽度: " << robot["width"].GetInt() << std::endl;}// 输出机器人长度if (robot.HasMember("length") && robot["length"].IsInt()) {std::cout << "长度: " << robot["length"].GetInt() << std::endl;}// 输出机器人半径if (robot.HasMember("radius") && robot["radius"].IsInt()) {std::cout << "半径: " << robot["radius"].GetInt() << std::endl;}}// 解析"other"部分if (document.HasMember("other") && document["other"].IsArray()) {std::cout << "其他信息: " << (document["other"].Empty() ? "空" : "存在内容") << std::endl;}// 关闭文件流ifs.close();
}int main() {// 调用解析函数,传入JSON文件所在目录路径和文件名parseJSON("", "test.json");return 0;
}
(2)在与main.cpp的同一目录下,将以下内容存储为test.json
{"Info": {"author": "JZX_MY","version": "v1.0.96","number": 66,"position": [-9, 60.0, 10.0],"rate": 0.99},"scale": 77,"regions": [{"index": 0,"name": "free_regions","points": [{"x": -2.03,"y": 2.25},{"x": 1.39,"y": 5.82},{"x": 7.47,"y": 2.35},{"x": 5.50,"y": -1.36}]},{"index": 1,"name": "occupy_regions","points": [{"x": -2.03,"y": 27.25},{"x": 10.39,"y": 5.82},{"x": 78.47,"y": 2.35}]}],"param": {"max_threshold": 0.05,"max_num" : 10240,"success" : true},"robot": {"width": 1510,"length": 5180,"radius": 3420},"other": []
}
(3)在存储以上两个文件的目录下的终端运行以下指令进行编译
g++ main.cpp -o main
(4)继续在该终端下,运行以下指令,运行程序
./main
(5)可以看到如下所示的运行结果
Info 部分解析结果:
作者: JZX_MY
版本: v1.0.96
编号: 66
位置: -9 60 10
速率: 0.99
缩放比例: 77
区域解析:
区域索引: 0
区域名称: free_regions
区域点坐标:
x: -2.03, y: 2.25
x: 1.39, y: 5.82
x: 7.47, y: 2.35
x: 5.5, y: -1.36
区域索引: 1
区域名称: occupy_regions
区域点坐标:
x: -2.03, y: 27.25
x: 10.39, y: 5.82
x: 78.47, y: 2.35
参数解析:
最大阈值: 0.05
最大数量: 10240
是否成功: 是
机器人参数:
宽度: 1510
长度: 5180
半径: 3420
其他信息: 空
以上综合示例的相关文件,我已经放在了本文的绑定附件中,有需要可以自行获取。
相关文章:

详细介绍如何使用rapidjson读取json文件
本文主要详细介绍如何使用rapidjson库来实现.json文件的读取,分为相关基础介绍、结合简单示例进行基础介绍、结合复杂示例进行详细的函数实现介绍等三部分。 一、相关基础 1、Json文件中的{} 和 [] 在 JSON 文件中,{} 和 [] 分别表示不同的数据结构&…...

【Qt】显示类控件:QLabel、QLCDNumber、QProgressBar、QCalendarWidget
目录 QLabel QFrame 例子: textFormat pixmap、scaledContents alignment wordWrap、indent、margin buddy QLCDNumber 例子: QTimer QProgressBar 例子: QCalendarWidget 例子: QLabel 标签控件,用来显示…...

设计模式-访问者设计模式
介绍 访问者模式(Visitor),表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变个元素的类的前提下定义作用于这些元素的新操作。 问题:在一个机构里面有两种员工,1.Teacher 2.Engineer 员…...

Spring框架IOC
目录 一、Spring框架的介绍 1.1 Spring框架的概述 1.2 Spring框架的优点 二、Spring的核心 IOC技术 2.1 什么是IOC 2.2 IOC的程序入门 2.3 IOC技术总结 2.4 Spring框架的Bean管理的配置文件方式 一、Spring框架的介绍 1.1 Spring框架的概述 Spring是一个开放源代码的…...

有哪些免费的 ERP 软件可供选择?哪些 ERP 软件使用体验较好?
想找个 “免费” 的 ERP 软件? 咱得知道,ERP 那可是涉及财务、人力、供应链、采购、销售等好多方面的重要企业软件。功能这么全,能免费才怪呢!真要是有免费的,早就火遍大江南北,说不定把市场都垄断了&…...
思科CCNA认证都学什么考什么?
关注 工 仲 好:IT运维大本营CCNA考试要学的东西很多,你不要看它只是一个初级认证,但是它的专业内容知识是不少的,你想要学好也是需要下一番苦功的。 那么考CCNA需要学哪些东西呢?下面我们就来了解一下吧。 01、考CCN…...

模型部署学习笔记——模型部署关键知识点总结
模型部署学习笔记——模型部署关键知识点总结 模型部署学习笔记——模型部署关键知识点总结1. CUDA中Grid和Block的定义是什么?Shared Memory的定义?Bank Conflict的定义?Stream和Event的定义?2. TensorRT的工作流程?3…...
22智能 狄克斯特拉算法复习
狄克斯特拉算法 图 根据边有无方向分为: 有向图、无向图 根据边有无权重变量分为: 有权图、无权图 根据顶点是否连通分为: 连通图和非连通图入度:表示有多少条边指向该顶点出度:表示有多少条边从该顶点指出算法步骤&a…...

首个!艾灵参编的工业边缘计算国家标准正式发布
近日,艾灵参与编制的《面向工业应用的边缘计算 应用指南》(以下简称《标准》)国家标准正式发布,将于2025年5月1日起实施。这一里程碑式的成果,不仅标志着我国在工业边缘计算技术标准化领域取得了重大突破,成…...
curl也支持断点续传
curl断点续传 访问外网资源,特别是Github上比较大的资源,例如,笔者遇到的calico发布包,经常会遇到在浏览器上下载半途中断。 那么支持断点续传的下载工具,就是应对这种情况的好帮手! 简单的断点续传工具…...

交换机链路聚合(手动负载分担模式)(eNSP)
目录 交换机SW_C配置: 交换机-PC划分vlan: 交换机-交换机端口聚合: 交换机SW_D配置: 交换机-PC划分vlan: 交换机-交换机端口聚合: 验证: 链路聚合的端口清除: 交换机端口聚合的存在意义主要有以下几点: 增加带宽 提高冗余性和可靠性 实现负载均衡 降低成本 …...

jmeter 接口性能测试 学习笔记
目录 说明工具准备工具配置jmeter 界面汉化配置汉化步骤汉化结果图 案例1:测试接口接口准备线程组添加线程组配置线程组值线程数(Number of Threads)Ramp-Up 时间(Ramp-Up Period)循环次数(Loop Count&…...
`HashMap`、`Hashtable` 和 `HashSet`的区别
HashMap、Hashtable 和 HashSet 都是 Java 中常用的集合类,它们的功能和实现有所不同,尽管它们都使用哈希表(hash table)作为底层数据结构。以下是它们之间的主要区别: 1. HashMap 和 Hashtable 的区别 特性HashMapH…...

Arduino中解析JSON数据
JSON JSON(JavaScript Object Notation,即JavaScript对象表示法)是一种广泛采用的开放标准文件格式与数据交换格式。它兼具人类可读性和机器易解析性,使得数据的编写、阅读、生成及解析都变得十分便捷。JSON的设计不依赖于特定编…...

linux----文件访问(c语言)
linux文件访问相关函数 打开文件函数 - open 函数原型:int open(const char *pathname, int flags, mode_t mode);参数说明: pathname:这是要打开的文件的路径名,可以是绝对路径或者相对路径。例如,"/home/user/…...
源码分析之Openlayers中MousePosition鼠标位置控件
概述 本文主要介绍 Openlayers 中的MousePosition鼠标位置控件,该控件会创建一个元素在页面的右上方用来实时显示鼠标光标的位置坐标。该控件在实际应用很有效,可以实时获取鼠标位置,但是一般控件元素都会自定义。 源码分析 MousePosition…...

以ATTCK为例构建网络安全知识图
ATT&CK(Adversarial Tactics, Techniques, and Common Knowledge )是一个攻击行为知识库和模型,主要应用于评估攻防能力覆盖、APT情报分析、威胁狩猎及攻击模拟等领域。本文简单介绍ATT&CK相关的背景概念,并探讨通过ATT&a…...
myexcel的使用
参考: (1)api文档:https://www.bookstack.cn/read/MyExcel-2.x/624d8ce73162300b.md (2)源代码: https://github.com/liaochong/myexcel/issues 我: (1)m…...
Unity 上好用的插件
PlayerMaker BehaviorDesigner Cinemachine Timeline Hybrid Addressable AssetBundle Blower Simple Zoom 大地图上缩放和平移使用ScrollRect的好效果实现...

Vivado - 远程调试 + 远程综合实现 + vmWare网络配置 + NFS 文件共享 + 使用 VIO 核
目录 1. 简介 2. VIO 配置 2.1 VIO IP 2.2 VIO 对比 ILA 3. VIO 示例 3.1 Led 3.1.1 工程配置 3.1.2 效果展示 3.2 Key 3.2.1 工程配置 3.2.1 效果展示 3.3 门控触发 3.3.1 工程配置 3.3.2 效果展示 4. 远程调试 4.1 配置目标主机 4.2 配置本机 4.3 vmWare 网…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...
Ubuntu系统下交叉编译openssl
一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机:Ubuntu 20.04.6 LTSHost:ARM32位交叉编译器:arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...
【Linux】shell脚本忽略错误继续执行
在 shell 脚本中,可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行,可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令,并忽略错误 rm somefile…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...
逻辑回归:给不确定性划界的分类大师
想象你是一名医生。面对患者的检查报告(肿瘤大小、血液指标),你需要做出一个**决定性判断**:恶性还是良性?这种“非黑即白”的抉择,正是**逻辑回归(Logistic Regression)** 的战场&a…...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...

全球首个30米分辨率湿地数据集(2000—2022)
数据简介 今天我们分享的数据是全球30米分辨率湿地数据集,包含8种湿地亚类,该数据以0.5X0.5的瓦片存储,我们整理了所有属于中国的瓦片名称与其对应省份,方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

MMaDA: Multimodal Large Diffusion Language Models
CODE : https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA,它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...