fixed adding file problem
[c11concurrency-benchmarks.git] / gdax-orderbook-hpp / demo / dependencies / rapidjson-1.1.0 / doc / tutorial.zh-cn.md
1 # 教程
2
3 本教程简介文件对象模型(Document Object Model, DOM)API。
4
5 如 [用法一览](../readme.zh-cn.md#用法一览) 中所示,可以解析一个 JSON 至 DOM,然后就可以轻松查询及修改 DOM,并最终转换回 JSON。
6
7 [TOC]
8
9 # Value 及 Document {#ValueDocument}
10
11 每个 JSON 值都储存为 `Value` 类,而 `Document` 类则表示整个 DOM,它存储了一个 DOM 树的根 `Value`。RapidJSON 的所有公开类型及函数都在 `rapidjson` 命名空间中。
12
13 # 查询 Value {#QueryValue}
14
15 在本节中,我们会使用到 `example/tutorial/tutorial.cpp` 中的代码片段。
16
17 假设我们用 C 语言的字符串储存一个 JSON(`const char* json`):
18 ~~~~~~~~~~js
19 {
20     "hello": "world",
21     "t": true ,
22     "f": false,
23     "n": null,
24     "i": 123,
25     "pi": 3.1416,
26     "a": [1, 2, 3, 4]
27 }
28 ~~~~~~~~~~
29
30 把它解析至一个 `Document`:
31 ~~~~~~~~~~cpp
32 #include "rapidjson/document.h"
33
34 using namespace rapidjson;
35
36 // ...
37 Document document;
38 document.Parse(json);
39 ~~~~~~~~~~
40
41 那么现在该 JSON 就会被解析至 `document` 中,成为一棵 *DOM 树 *:
42
43 ![教程中的 DOM](diagram/tutorial.png)
44
45 自从 RFC 7159 作出更新,合法 JSON 文件的根可以是任何类型的 JSON 值。而在较早的 RFC 4627 中,根值只允许是 Object 或 Array。而在上述例子中,根是一个 Object。
46 ~~~~~~~~~~cpp
47 assert(document.IsObject());
48 ~~~~~~~~~~
49
50 让我们查询一下根 Object 中有没有 `"hello"` 成员。由于一个 `Value` 可包含不同类型的值,我们可能需要验证它的类型,并使用合适的 API 去获取其值。在此例中,`"hello"` 成员关联到一个 JSON String。
51 ~~~~~~~~~~cpp
52 assert(document.HasMember("hello"));
53 assert(document["hello"].IsString());
54 printf("hello = %s\n", document["hello"].GetString());
55 ~~~~~~~~~~
56
57 ~~~~~~~~~~
58 world
59 ~~~~~~~~~~
60
61 JSON True/False 值是以 `bool` 表示的。
62 ~~~~~~~~~~cpp
63 assert(document["t"].IsBool());
64 printf("t = %s\n", document["t"].GetBool() ? "true" : "false");
65 ~~~~~~~~~~
66
67 ~~~~~~~~~~
68 true
69 ~~~~~~~~~~
70
71 JSON Null 值可用 `IsNull()` 查询。
72 ~~~~~~~~~~cpp
73 printf("n = %s\n", document["n"].IsNull() ? "null" : "?");
74 ~~~~~~~~~~
75
76 ~~~~~~~~~~
77 null
78 ~~~~~~~~~~
79
80 JSON Number 类型表示所有数值。然而,C++ 需要使用更专门的类型。
81
82 ~~~~~~~~~~cpp
83 assert(document["i"].IsNumber());
84
85 // 在此情况下,IsUint()/IsInt64()/IsUInt64() 也会返回 true
86 assert(document["i"].IsInt());          
87 printf("i = %d\n", document["i"].GetInt());
88 // 另一种用法: (int)document["i"]
89
90 assert(document["pi"].IsNumber());
91 assert(document["pi"].IsDouble());
92 printf("pi = %g\n", document["pi"].GetDouble());
93 ~~~~~~~~~~
94
95 ~~~~~~~~~~
96 i = 123
97 pi = 3.1416
98 ~~~~~~~~~~
99
100 JSON Array 包含一些元素。
101 ~~~~~~~~~~cpp
102 // 使用引用来连续访问,方便之余还更高效。
103 const Value& a = document["a"];
104 assert(a.IsArray());
105 for (SizeType i = 0; i < a.Size(); i++) // 使用 SizeType 而不是 size_t
106         printf("a[%d] = %d\n", i, a[i].GetInt());
107 ~~~~~~~~~~
108
109 ~~~~~~~~~~
110 a[0] = 1
111 a[1] = 2
112 a[2] = 3
113 a[3] = 4
114 ~~~~~~~~~~
115
116 注意,RapidJSON 并不自动转换各种 JSON 类型。例如,对一个 String 的 Value 调用 `GetInt()` 是非法的。在调试模式下,它会被断言失败。在发布模式下,其行为是未定义的。
117
118 以下将会讨论有关查询各类型的细节。
119
120 ## 查询 Array {#QueryArray}
121
122 缺省情况下,`SizeType` 是 `unsigned` 的 typedef。在多数系统中,Array 最多能存储 2^32-1 个元素。
123
124 你可以用整数字面量访问元素,如 `a[0]`、`a[1]`、`a[2]`。
125
126 Array 与 `std::vector` 相似,除了使用索引,也可使用迭代器来访问所有元素。
127 ~~~~~~~~~~cpp
128 for (Value::ConstValueIterator itr = a.Begin(); itr != a.End(); ++itr)
129     printf("%d ", itr->GetInt());
130 ~~~~~~~~~~
131
132 还有一些熟悉的查询函数:
133 * `SizeType Capacity() const`
134 * `bool Empty() const`
135
136 ### 范围 for 循环 (v1.1.0 中的新功能)
137
138 当使用 C++11 功能时,你可使用范围 for 循环去访问 Array 内的所有元素。
139
140 ~~~~~~~~~~cpp
141 for (auto& v : a.GetArray())
142     printf("%d ", v.GetInt());
143 ~~~~~~~~~~
144
145 ## 查询 Object {#QueryObject}
146
147 和 Array 相似,我们可以用迭代器去访问所有 Object 成员:
148
149 ~~~~~~~~~~cpp
150 static const char* kTypeNames[] = 
151     { "Null", "False", "True", "Object", "Array", "String", "Number" };
152
153 for (Value::ConstMemberIterator itr = document.MemberBegin();
154     itr != document.MemberEnd(); ++itr)
155 {
156     printf("Type of member %s is %s\n",
157         itr->name.GetString(), kTypeNames[itr->value.GetType()]);
158 }
159 ~~~~~~~~~~
160
161 ~~~~~~~~~~
162 Type of member hello is String
163 Type of member t is True
164 Type of member f is False
165 Type of member n is Null
166 Type of member i is Number
167 Type of member pi is Number
168 Type of member a is Array
169 ~~~~~~~~~~
170
171 注意,当 `operator[](const char*)` 找不到成员,它会断言失败。
172
173 若我们不确定一个成员是否存在,便需要在调用 `operator[](const char*)` 前先调用 `HasMember()`。然而,这会导致两次查找。更好的做法是调用 `FindMember()`,它能同时检查成员是否存在并返回它的 Value:
174
175 ~~~~~~~~~~cpp
176 Value::ConstMemberIterator itr = document.FindMember("hello");
177 if (itr != document.MemberEnd())
178     printf("%s\n", itr->value.GetString());
179 ~~~~~~~~~~
180
181 ### 范围 for 循环 (v1.1.0 中的新功能)
182
183 当使用 C++11 功能时,你可使用范围 for 循环去访问 Object 内的所有成员。
184
185 ~~~~~~~~~~cpp
186 for (auto& m : document.GetObject())
187     printf("Type of member %s is %s\n",
188         m.name.GetString(), kTypeNames[m.value.GetType()]);
189 ~~~~~~~~~~
190
191 ## 查询 Number {#QueryNumber}
192
193 JSON 只提供一种数值类型──Number。数字可以是整数或实数。RFC 4627 规定数字的范围由解析器指定。
194
195 由于 C++ 提供多种整数及浮点数类型,DOM 尝试尽量提供最广的范围及良好性能。
196
197 当解析一个 Number 时, 它会被存储在 DOM 之中,成为下列其中一个类型:
198
199 类型       | 描述
200 -----------|---------------------------------------
201 `unsigned` | 32 位无号整数
202 `int`      | 32 位有号整数
203 `uint64_t` | 64 位无号整数
204 `int64_t`  | 64 位有号整数
205 `double`   | 64 位双精度浮点数
206
207 当查询一个 Number 时, 你可以检查该数字是否能以目标类型来提取:
208
209 查检              | 提取
210 ------------------|---------------------
211 `bool IsNumber()` | 不适用
212 `bool IsUint()`   | `unsigned GetUint()`
213 `bool IsInt()`    | `int GetInt()`
214 `bool IsUint64()` | `uint64_t GetUint64()`
215 `bool IsInt64()`  | `int64_t GetInt64()`
216 `bool IsDouble()` | `double GetDouble()`
217
218 注意,一个整数可能用几种类型来提取,而无需转换。例如,一个名为 `x` 的 Value 包含 123,那么 `x.IsInt() == x.IsUint() == x.IsInt64() == x.IsUint64() == true`。但如果一个名为 `y` 的 Value 包含 -3000000000,那么仅会令 `x.IsInt64() == true`。
219
220 当要提取 Number 类型,`GetDouble()` 是会把内部整数的表示转换成 `double`。注意 `int` 和 `unsigned` 可以安全地转换至 `double`,但 `int64_t` 及 `uint64_t` 可能会丧失精度(因为 `double` 的尾数只有 52 位)。
221
222 ## 查询 String {#QueryString}
223
224 除了 `GetString()`,`Value` 类也有一个 `GetStringLength()`。这里会解释个中原因。
225
226 根据 RFC 4627,JSON String 可包含 Unicode 字符 `U+0000`,在 JSON 中会表示为 `"\u0000"`。问题是,C/C++ 通常使用空字符结尾字符串(null-terminated string),这种字符串把 ``\0'` 作为结束符号。
227
228 为了符合 RFC 4627,RapidJSON 支持包含 `U+0000` 的 String。若你需要处理这些 String,便可使用 `GetStringLength()` 去获得正确的字符串长度。
229
230 例如,当解析以下的 JSON 至 `Document d` 之后:
231
232 ~~~~~~~~~~js
233 { "s" :  "a\u0000b" }
234 ~~~~~~~~~~
235 `"a\u0000b"` 值的正确长度应该是 3。但 `strlen()` 会返回 1。
236
237 `GetStringLength()` 也可以提高性能,因为用户可能需要调用 `strlen()` 去分配缓冲。
238
239 此外,`std::string` 也支持这个构造函数:
240
241 ~~~~~~~~~~cpp
242 string(const char* s, size_t count);
243 ~~~~~~~~~~
244
245 此构造函数接受字符串长度作为参数。它支持在字符串中存储空字符,也应该会有更好的性能。
246
247 ## 比较两个 Value
248
249 你可使用 `==` 及 `!=` 去比较两个 Value。当且仅当两个 Value 的类型及内容相同,它们才当作相等。你也可以比较 Value 和它的原生类型值。以下是一个例子。
250
251 ~~~~~~~~~~cpp
252 if (document["hello"] == document["n"]) /*...*/;    // 比较两个值
253 if (document["hello"] == "world") /*...*/;          // 与字符串家面量作比较
254 if (document["i"] != 123) /*...*/;                  // 与整数作比较
255 if (document["pi"] != 3.14) /*...*/;                // 与 double 作比较
256 ~~~~~~~~~~
257
258 Array/Object 顺序以它们的元素/成员作比较。当且仅当它们的整个子树相等,它们才当作相等。
259
260 注意,现时若一个 Object 含有重复命名的成员,它与任何 Object 作比较都总会返回 `false`。
261
262 # 创建/修改值 {#CreateModifyValues}
263
264 有多种方法去创建值。 当一个 DOM 树被创建或修改后,可使用 `Writer` 再次存储为 JSON。
265
266 ## 改变 Value 类型 {#ChangeValueType}
267 当使用默认构造函数创建一个 Value 或 Document,它的类型便会是 Null。要改变其类型,需调用 `SetXXX()` 或赋值操作,例如:
268
269 ~~~~~~~~~~cpp
270 Document d; // Null
271 d.SetObject();
272
273 Value v;    // Null
274 v.SetInt(10);
275 v = 10;     // 简写,和上面的相同
276 ~~~~~~~~~~
277
278 ### 构造函数的各个重载
279 几个类型也有重载构造函数:
280
281 ~~~~~~~~~~cpp
282 Value b(true);    // 调用 Value(bool)
283 Value i(-123);    // 调用 Value(int)
284 Value u(123u);    // 调用 Value(unsigned)
285 Value d(1.5);     // 调用 Value(double)
286 ~~~~~~~~~~
287
288 要重建空 Object 或 Array,可在默认构造函数后使用 `SetObject()`/`SetArray()`,或一次性使用 `Value(Type)`:
289
290 ~~~~~~~~~~cpp
291 Value o(kObjectType);
292 Value a(kArrayType);
293 ~~~~~~~~~~
294
295 ## 转移语意(Move Semantics) {#MoveSemantics}
296
297 在设计 RapidJSON 时有一个非常特别的决定,就是 Value 赋值并不是把来源 Value 复制至目的 Value,而是把把来源 Value 转移(move)至目的 Value。例如:
298
299 ~~~~~~~~~~cpp
300 Value a(123);
301 Value b(456);
302 b = a;         // a 变成 Null,b 变成数字 123。
303 ~~~~~~~~~~
304
305 ![使用移动语意赋值。](diagram/move1.png)
306
307 为什么?此语意有何优点?
308
309 最简单的答案就是性能。对于固定大小的 JSON 类型(Number、True、False、Null),复制它们是简单快捷。然而,对于可变大小的 JSON 类型(String、Array、Object),复制它们会产生大量开销,而且这些开销常常不被察觉。尤其是当我们需要创建临时 Object,把它复制至另一变量,然后再析构它。
310
311 例如,若使用正常 * 复制 * 语意:
312
313 ~~~~~~~~~~cpp
314 Value o(kObjectType);
315 {
316     Value contacts(kArrayType);
317     // 把元素加进 contacts 数组。
318     // ...
319     o.AddMember("contacts", contacts, d.GetAllocator());  // 深度复制 contacts (可能有大量内存分配)
320     // 析构 contacts。
321 }
322 ~~~~~~~~~~
323
324 ![复制语意产生大量的复制操作。](diagram/move2.png)
325
326 那个 `o` Object 需要分配一个和 contacts 相同大小的缓冲区,对 conacts 做深度复制,并最终要析构 contacts。这样会产生大量无必要的内存分配/释放,以及内存复制。
327
328 有一些方案可避免实质地复制这些数据,例如引用计数(reference counting)、垃圾回收(garbage collection, GC)。
329
330 为了使 RapidJSON 简单及快速,我们选择了对赋值采用 * 转移 * 语意。这方法与 `std::auto_ptr` 相似,都是在赋值时转移拥有权。转移快得多简单得多,只需要析构原来的 Value,把来源 `memcpy()` 至目标,最后把来源设置为 Null 类型。
331
332 因此,使用转移语意后,上面的例子变成:
333
334 ~~~~~~~~~~cpp
335 Value o(kObjectType);
336 {
337     Value contacts(kArrayType);
338     // adding elements to contacts array.
339     o.AddMember("contacts", contacts, d.GetAllocator());  // 只需 memcpy() contacts 本身至新成员的 Value(16 字节)
340     // contacts 在这里变成 Null。它的析构是平凡的。
341 }
342 ~~~~~~~~~~
343
344 ![转移语意不需复制。](diagram/move3.png)
345
346 在 C++11 中这称为转移赋值操作(move assignment operator)。由于 RapidJSON 支持 C++03,它在赋值操作采用转移语意,其它修改形函数如 `AddMember()`, `PushBack()` 也采用转移语意。
347
348 ### 转移语意及临时值 {#TemporaryValues}
349
350 有时候,我们想直接构造一个 Value 并传递给一个“转移”函数(如 `PushBack()`、`AddMember()`)。由于临时对象是不能转换为正常的 Value 引用,我们加入了一个方便的 `Move()` 函数:
351
352 ~~~~~~~~~~cpp
353 Value a(kArrayType);
354 Document::AllocatorType& allocator = document.GetAllocator();
355 // a.PushBack(Value(42), allocator);       // 不能通过编译
356 a.PushBack(Value().SetInt(42), allocator); // fluent API
357 a.PushBack(Value(42).Move(), allocator);   // 和上一行相同
358 ~~~~~~~~~~
359
360 ## 创建 String {#CreateString}
361 RapidJSON 提供两个 String 的存储策略。
362
363 1. copy-string: 分配缓冲区,然后把来源数据复制至它。
364 2. const-string: 简单地储存字符串的指针。
365
366 Copy-string 总是安全的,因为它拥有数据的克隆。Const-string 可用于存储字符串字面量,以及用于在 DOM 一节中将会提到的 in-situ 解析中。
367
368 为了让用户自定义内存分配方式,当一个操作可能需要内存分配时,RapidJSON 要求用户传递一个 allocator 实例作为 API 参数。此设计避免了在每个 Value 存储 allocator(或 document)的指针。
369
370 因此,当我们把一个 copy-string 赋值时, 调用含有 allocator 的 `SetString()` 重载函数:
371
372 ~~~~~~~~~~cpp
373 Document document;
374 Value author;
375 char buffer[10];
376 int len = sprintf(buffer, "%s %s", "Milo", "Yip"); // 动态创建的字符串。
377 author.SetString(buffer, len, document.GetAllocator());
378 memset(buffer, 0, sizeof(buffer));
379 // 清空 buffer 后 author.GetString() 仍然包含 "Milo Yip"
380 ~~~~~~~~~~
381
382 在此例子中,我们使用 `Document` 实例的 allocator。这是使用 RapidJSON 时常用的惯用法。但你也可以用其他 allocator 实例。
383
384 另外,上面的 `SetString()` 需要长度参数。这个 API 能处理含有空字符的字符串。另一个 `SetString()` 重载函数没有长度参数,它假设输入是空字符结尾的,并会调用类似 `strlen()` 的函数去获取长度。
385
386 最后,对于字符串字面量或有安全生命周期的字符串,可以使用 const-string 版本的 `SetString()`,它没有 allocator 参数。对于字符串家面量(或字符数组常量),只需简单地传递字面量,又安全又高效:
387
388 ~~~~~~~~~~cpp
389 Value s;
390 s.SetString("rapidjson");    // 可包含空字符,长度在编译萁推导
391 s = "rapidjson";             // 上行的缩写
392 ~~~~~~~~~~
393
394 对于字符指针,RapidJSON 需要作一个标记,代表它不复制也是安全的。可以使用 `StringRef` 函数:
395
396 ~~~~~~~~~cpp
397 const char * cstr = getenv("USER");
398 size_t cstr_len = ...;                 // 如果有长度
399 Value s;
400 // s.SetString(cstr);                  // 这不能通过编译
401 s.SetString(StringRef(cstr));          // 可以,假设它的生命周期安全,并且是以空字符结尾的
402 s = StringRef(cstr);                   // 上行的缩写
403 s.SetString(StringRef(cstr, cstr_len));// 更快,可处理空字符
404 s = StringRef(cstr, cstr_len);         // 上行的缩写
405
406 ~~~~~~~~~
407
408 ## 修改 Array {#ModifyArray}
409 Array 类型的 Value 提供与 `std::vector` 相似的 API。
410
411 * `Clear()`
412 * `Reserve(SizeType, Allocator&)`
413 * `Value& PushBack(Value&, Allocator&)`
414 * `template <typename T> GenericValue& PushBack(T, Allocator&)`
415 * `Value& PopBack()`
416 * `ValueIterator Erase(ConstValueIterator pos)`
417 * `ValueIterator Erase(ConstValueIterator first, ConstValueIterator last)`
418
419 注意,`Reserve(...)` 及 `PushBack(...)` 可能会为数组元素分配内存,所以需要一个 allocator。
420
421 以下是 `PushBack()` 的例子:
422
423 ~~~~~~~~~~cpp
424 Value a(kArrayType);
425 Document::AllocatorType& allocator = document.GetAllocator();
426
427 for (int i = 5; i <= 10; i++)
428     a.PushBack(i, allocator);   // 可能需要调用 realloc() 所以需要 allocator
429
430 // 流畅接口(Fluent interface)
431 a.PushBack("Lua", allocator).PushBack("Mio", allocator);
432 ~~~~~~~~~~
433
434 与 STL 不一样的是,`PushBack()`/`PopBack()` 返回 Array 本身的引用。这称为流畅接口(_fluent interface_)。
435
436 如果你想在 Array 中加入一个非常量字符串,或是一个没有足够生命周期的字符串(见 [Create String](#CreateString)),你需要使用 copy-string API 去创建一个 String。为了避免加入中间变量,可以就地使用一个 [临时值](#TemporaryValues):
437
438 ~~~~~~~~~~cpp
439 // 就地 Value 参数
440 contact.PushBack(Value("copy", document.GetAllocator()).Move(), // copy string
441                  document.GetAllocator());
442
443 // 显式 Value 参数
444 Value val("key", document.GetAllocator()); // copy string
445 contact.PushBack(val, document.GetAllocator());
446 ~~~~~~~~~~
447
448 ## 修改 Object {#ModifyObject}
449 Object 是键值对的集合。每个键必须为 String。要修改 Object,方法是增加或移除成员。以下的 API 用来增加城员:
450
451 * `Value& AddMember(Value&, Value&, Allocator& allocator)`
452 * `Value& AddMember(StringRefType, Value&, Allocator&)`
453 * `template <typename T> Value& AddMember(StringRefType, T value, Allocator&)`
454
455 以下是一个例子。
456
457 ~~~~~~~~~~cpp
458 Value contact(kObject);
459 contact.AddMember("name", "Milo", document.GetAllocator());
460 contact.AddMember("married", true, document.GetAllocator());
461 ~~~~~~~~~~
462
463 使用 `StringRefType` 作为 name 参数的重载版本与字符串的 `SetString` 的接口相似。 这些重载是为了避免复制 `name` 字符串,因为 JSON object 中经常会使用常数键名。
464
465 如果你需要从非常数字符串或生命周期不足的字符串创建键名(见 [创建 String](#CreateString)),你需要使用 copy-string API。为了避免中间变量,可以就地使用 [临时值](#TemporaryValues):
466
467 ~~~~~~~~~~cpp
468 // 就地 Value 参数
469 contact.AddMember(Value("copy", document.GetAllocator()).Move(), // copy string
470                   Value().Move(),                                // null value
471                   document.GetAllocator());
472
473 // 显式参数
474 Value key("key", document.GetAllocator()); // copy string name
475 Value val(42);                             // 某 Value
476 contact.AddMember(key, val, document.GetAllocator());
477 ~~~~~~~~~~
478
479 移除成员有几个选择:
480
481 * `bool RemoveMember(const Ch* name)`:使用键名来移除成员(线性时间复杂度)。
482 * `bool RemoveMember(const Value& name)`:除了 `name` 是一个 Value,和上一行相同。
483 * `MemberIterator RemoveMember(MemberIterator)`:使用迭代器移除成员(_ 常数 _ 时间复杂度)。
484 * `MemberIterator EraseMember(MemberIterator)`:和上行相似但维持成员次序(线性时间复杂度)。
485 * `MemberIterator EraseMember(MemberIterator first, MemberIterator last)`:移除一个范围内的成员,维持次序(线性时间复杂度)。
486
487 `MemberIterator RemoveMember(MemberIterator)` 使用了“转移最后”手法来达成常数时间复杂度。基本上就是析构迭代器位置的成员,然后把最后的成员转移至迭代器位置。因此,成员的次序会被改变。
488
489 ## 深复制 Value {#DeepCopyValue}
490 若我们真的要复制一个 DOM 树,我们可使用两个 APIs 作深复制:含 allocator 的构造函数及 `CopyFrom()`。
491
492 ~~~~~~~~~~cpp
493 Document d;
494 Document::AllocatorType& a = d.GetAllocator();
495 Value v1("foo");
496 // Value v2(v1); // 不容许
497
498 Value v2(v1, a);                      // 制造一个克隆
499 assert(v1.IsString());                // v1 不变
500 d.SetArray().PushBack(v1, a).PushBack(v2, a);
501 assert(v1.IsNull() && v2.IsNull());   // 两个都转移动 d
502
503 v2.CopyFrom(d, a);                    // 把整个 document 复制至 v2
504 assert(d.IsArray() && d.Size() == 2); // d 不变
505 v1.SetObject().AddMember("array", v2, a);
506 d.PushBack(v1, a);
507 ~~~~~~~~~~
508
509 ## 交换 Value {#SwapValues}
510
511 RapidJSON 也提供 `Swap()`。
512
513 ~~~~~~~~~~cpp
514 Value a(123);
515 Value b("Hello");
516 a.Swap(b);
517 assert(a.IsString());
518 assert(b.IsInt());
519 ~~~~~~~~~~
520
521 无论两棵 DOM 树有多复杂,交换是很快的(常数时间)。
522
523 # 下一部分 {#WhatsNext}
524
525 本教程展示了如何询查及修改 DOM 树。RapidJSON 还有一个重要概念:
526
527 1. [流](doc/stream.zh-cn.md) 是读写 JSON 的通道。流可以是内存字符串、文件流等。用户也可以自定义流。
528 2. [编码](doc/encoding.zh-cn.md) 定义在流或内存中使用的字符编码。RapidJSON 也在内部提供 Unicode 转换及校验功能。
529 3. [DOM](doc/dom.zh-cn.md) 的基本功能已在本教程里介绍。还有更高级的功能,如原位(*in situ*)解析、其他解析选项及高级用法。
530 4. [SAX](doc/sax.zh-cn.md) 是 RapidJSON 解析/生成功能的基础。学习使用 `Reader`/`Writer` 去实现更高性能的应用程序。也可以使用 `PrettyWriter` 去格式化 JSON。
531 5. [性能](doc/performance.zh-cn.md) 展示一些我们做的及第三方的性能测试。
532 6. [技术内幕](doc/internals.md) 讲述一些 RapidJSON 内部的设计及技术。
533
534 你也可以参考 [常见问题](doc/faq.zh-cn.md)、API 文档、例子及单元测试。