详细介绍如何使用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 网…...

双臂机器人
目录 一、双臂机器人简介 二、双臂机器人系统的组成 三、双臂机器人面临的主要挑战 3.1 协调与协同控制问题 3.2 力控制与柔顺性问题 3.3 路径规划与轨迹优化问题 3.4 感知与环境交互 3.5 人机协作问题 3.6 能源与效率问题 3.7 稳定性与可靠性问题 四、双臂机器人…...

【Lua热更新】上篇
Lua 热更新 - 上篇 下篇链接:【Lua热更新】下篇 文章目录 Lua 热更新 - 上篇一、AssetBundle1.理论2. AB包资源加载 二、Lua 语法1. 简单数据类型2.字符串操作3.运算符4.条件分支语句5.循环语句6.函数7. table数组8.迭代器遍历9.复杂数据类型 - 表9.1字典9.2类9.3…...

Ubuntu批量修改文件名
文章目录 批量重命名文件:Ubuntu下使用find命令结合sed和mv参考 批量重命名文件:Ubuntu下使用find命令结合sed和mv 在日常开发和文件管理中,有时我们需要批量重命名一批文件,比如将文件名中的某个特定字符串替换为另一个字符串。…...

食家巷大烤馍:岁月沉淀下的麦香传奇
在繁华都市的街角巷尾,隐藏着许多不为人知的美食宝藏,食家巷大烤馍便是其中之一。它宛如一位低调的美食大师,默默散发着独特的魅力,用最质朴的味道,征服着每一个过往食客的味蕾。 初见食家巷大烤馍,你会被…...

harmony UI组件学习(1)
Image 图片组件 string格式,通常用来加载网络图片,需要申请网络访问权限:ohos.permission.INTERNET Image(https://xxx.png) PixelMap格式,可以加载像素图,常用在图片编辑中 Image(pixelMapobject) Resource格式,加…...

BTP Integration Suite CPI Apache Camel
官网文档: https://help.sap.com/docs/integration-suite/sap-integration-suite/what-is-sap-integration-suite CPI 云集成(CPI)有以下几个特性: SAP Cloud Integration通过消息交换支持端到端流程集成。 它基于Apache软件基金会的开源框架Camel。 …...

vitepress-打包SyntaxError: Element is missing end tag.
一、vitepress打包编译报错Element is missing end tag. 背景: 新增了一些笔记准备上传到git仓库,持续集成部署的时候,控制台报错了,错误信息如下: SyntaxError: Element is missing end tag. 仔细看了下控制台几乎没啥…...

【从零开始入门unity游戏开发之——C#篇21】C#面向对象的封装——`this`扩展方法、运算符重载、内部类、`partial` 定义分部类
文章目录 一、this扩展方法1、扩展方法的基本语法2、使用扩展方法3、扩展方法的注意事项5、扩展方法的限制6、总结 二、运算符重载1、C# 运算符重载2、运算符重载的基本语法3. 示例:重载加法运算符 ()4、使用重载的运算符5、支持重载的运算符6、不能重载的运算符7、…...

Java进程占用的内存有哪些部分?
大家好,我是锋哥。今天分享关于【Java进程占用的内存有哪些部分?】面试题。希望对大家有帮助; Java进程占用的内存有哪些部分? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Java进程在运行时,会将内存划分为多个区域…...

【华为OD机试真题】【2024年E卷】数值同化-队列BFS(C++/Java/Python)
文章目录 分值:200题目描述思路复杂度分析AC 代码 分值:200 题目描述 存在一个 m * n 的 二维数组只,其成员取值范围为0, 1, 2。其中值为1的元素具备同化特性,每经过1S,将上下左右值为0的元素同化为1。而值为2的元素…...