用 ChatGPT 打造最强 Rust 辅助学习“魔法”系统

建议朋友们先学习 《Prompt 指南》 之后阅读本文更好。建议使用 GPT-4，如果没有 GPT-4 ，用 3.5 也可以打造这个系统，只不过 GPT-4 更加精准一些。另外，这套系统你也可以应用到其他编程语言上面。 题图为本人使用 Midjourney 所画的《阿姆斯特丹港口的咖啡馆》。

引子#

最近在跟风玩 AI ，包括 ChatGPT 和 Midjourney，体验一下传说中年薪百万美元的 Prompt 工程师是什么感觉。直到这周，我发现 GPT4 对于 Rust 代码的辅助学习非常有帮助。于是，我就尝试打造这个 Rust 辅助学习系统，我将其命名为 RustChat ，在 GitHub 上创建了仓库，方便大家交流。

在我们学习 Rust 的时候，如果手头有一个称手的系统，可以随时出题来考察我们的知识点，并且能给出准确的答案和解释。或者，在我们不知道该做什么项目练手的时候，能给我们推荐项目 idea ，该多好。

现在有了 AI ，我们就可以打造这样一个神奇且有用的系统了。

RustChat : 最强 Rust 辅助学习 “魔法” 系统介绍#

先来讲一下思路。既然想要指令 prompt 模式，那么就应该像设计程序 API 一样，先把指令格式设计出来。

值得说明的是，这个思路看似比较简单，但是在大量尝试以后摸索出来的。之前失败的设计带来的结果就是某个新增的指令直接打破了 GPT 的旧设定，导致指令混乱。

“魔法” 设计#

以下列出我想要实现的指令 prompt 示例：

Copy
work // work 为主指令，代表输出工作级的 Rust 题目
work -tech trait // `-tech` 表示一级指令，代表要输出对指定内容 trait 的工作级概念讲解，并且附带代码示例
work -tech --cn trait // `--cn` 表示二级指令，代表将主指令和一级指令设定的输出内容以指定的语言中文来输出
work -type --en eBPF // `-type` 表示一级指令，代表要输出指定内容 eBPF 的工作级题目，
work -idea --cn // `-idea` 表示一级指令，代表要输出对应主指令级别的项目实践想法和思路供人参考, `--cn` 表示二级指令，代表以指定的语言中文来输出内容

题目我打算分为三个级别：

工作级别。工作级别问题是覆盖 入门以及日常编写 rust 代码的常用特性和知识。

专业级别。专业级别覆盖 Rust 专家级特性和知识点。

Quiz 类型。 Quiz 类专门考察容易令人迷惑的 Rust 语言特性的题。

主指令对应这三个级别分别为：

work 指令，对应 工作级别的题目。

expert 指令，对应专业级别题目。

quiz 指令，对应 Quiz 级别题目。

anwser 指令，用来指定应该回答的问题数。

接下来介绍一级指令，一级指令是可以和主指令进行任意组合：

-tech ，表示一级指令，代表要对其后指定的内容输出相应主指令级别的概念讲解，并附带示例。

-type ，表示一级指令，代表要对其后指定的内容输出相应主指令级别的题目。

-idea，表示一级指令，代表要输出对应主指令级别的项目实践想法和思路供人参考。

-crate，表示一级指令，，只限主指令 work 使用，代表要对其后指定的内容推荐相应的第三方 crate

-n ，表示一级指令，只限主指令 anwser 使用， 代表要回答的问题数目。

接下来介绍二级指令，二级指令可作用于一级指令后面：

--cn，表示二级指令，代表以中文来输出内容

--en，表示二级指令，代表以英文来输出内容

--jp，表示二级指令，代表以日文来输出内容

那么这个辅助学习系统应该支持的指令如下：

Copy
work // work 为主指令，代表输出工作级的 Rust 题目
work -tech trait // `-tech` 表示一级指令，代表要输出对指定内容 trait 的工作级概念讲解，并且附带代码示例
work -tech --cn trait // `--cn` 表示二级指令，代表将主指令和一级指令设定的输出内容以指定的语言中文来输出
work -type --en eBPF // `-type` 表示一级指令，代表要输出指定内容 eBPF 的工作级题目，
work -crate eBPF // `-crate` 表示一级指令，代表要推荐指定内容 eBPF 的第三方 crate
work -idea --cn // `-idea` 表示一级指令，代表要输出对应主指令级别的项目实践想法和思路供人参考, `--cn` 表示二级指令，代表以指定的语言中文来输出内容
anwser -n 2 ，// 表示一级指令，只限主指令 `anwser` 使用， 代表要回答当前这条指令前的两个问题
expert // work 为主指令，代表输出工作级的 Rust 题目
expert -tech trait // `-tech` 表示一级指令，代表要输出对指定内容 trait 的工作级概念讲解，并且附带代码示例
expert -tech --cn trait // `--cn` 表示二级指令，代表将主指令和一级指令设定的输出内容以指定的语言中文来输出
expert -type --en eBPF // `-type` 表示一级指令，代表要输出指定内容 eBPF 的工作级题目，
expert -idea --cn // `-idea` 表示一级指令，代表要输出对应主指令级别的项目实践想法和思路供人参考, `--cn` 表示二级指令，代表以指定的语言中文来输出内容
quiz // work 为主指令，代表输出工作级的 Rust 题目
quiz -tech trait // `-tech` 表示一级指令，代表要输出对指定内容 trait 的工作级概念讲解，并且附带代码示例
quiz -tech --cn trait // `--cn` 表示二级指令，代表将主指令和一级指令设定的输出内容以指定的语言中文来输出
quiz -type --en eBPF // `-type` 表示一级指令，代表要输出指定内容 eBPF 的工作级题目，
quiz -idea --cn // `-idea` 表示一级指令，代表要输出对应主指令级别的项目实践想法和思路供人参考, `--cn` 表示二级指令，代表以指定的语言中文来输出内容

打造 prompt#

接下来我们就来打造 prompt。 为了确保 GPT-4 可以完整地理解设计的 “魔法”，应该采取「整体到细节」的步骤逐步来 “喂” 它。

一、设定主指令

Copy
请你化身为 Rust 专家，来按我的要求帮我生成 Rust 题目，题目默认包括一道选择题和一道编写代码实现的题。默认以中文输出，除非指定了相应的二级指令来告诉你题目数量。

题目我打算分为三个级别：

1. 工作级别。工作级别问题是覆盖 入门以及日常编写 rust 代码的常用特性和知识。
2. 专业级别。专业级别覆盖 Rust 专家级特性和知识点。
3. Quiz 类型。 Quiz 类专门考察容易令人迷惑的 Rust 语言特性的题。

主指令对应这三个级别分别为：

1. work 指令，对应 工作级别的题目。
2. expert 指令，对应专业级别题目。
3. quiz 指令，对应 Quiz 级别题目。
4. anwser 指令，用来指定应该回答的问题数。

二、设定二级指令

Copy
接下来介绍一级指令，一级指令是可以和主指令组合使用：
1. `-tech` ，表示一级指令，代表要对其后指定的内容输出相应主指令级别的概念讲解，并附带示例。
2. `-type` ，表示一级指令，代表要对其后指定的内容输出相应主指令级别的题目。
3. `-idea`，表示一级指令，代表要输出对应主指令级别的项目实践想法和思路供人参考。
4. `-crate`，表示一级指令，代表要对其后指定的内容推荐相应的第三方 crate
5. `-n` ，表示一级指令， 代表你要给出答案的问题数目，并附上对答案的解释。用到其他主指令的话，就是限制生成问题数量。

三、设定三级指令

Copy
接下来介绍二级指令，二级指令可作用于一级指令后面：
1. `--cn`，表示二级指令，代表以中文来输出内容
2. `--en`，表示二级指令，代表以英文来输出内容
3. `--jp`，表示二级指令，代表以日文来输出内容

四、增加修改指令定义

之所以这么费劲地设计这套指令系统，就是为了增加修改指令更加方便。否则，GPT-4 会乱套的。

下面是一个修改指令的示例：

Copy
修改一级指令：

`-n` ，表示一级指令， 代表你要给出答案的问题数目，并附上对答案的解释。用到其他主指令的话，就是限制生成问题数量。

下面是一个增加指令的示例：

Copy
增加主指令：

explain ， 表示对指定的 rust 代码进行详细的解释
debug， 表示按给定的编译器错误提示来帮助查找代码bug并给出修正代码

说明： explain 本身有解释的意思，所以就算不这样设置主指令，GPT 也能识别这个单词。通常情况下 explain --cn 这种方式 GPT 也能识别 cn 并输出中文。

使用方式：

Copy
debug

fn add(a: i32, b: i32) -> i32 {
(a + b) as i64
}

error: 返回类型不匹配

让 GPT 输出 “魔法” 系统说明书#

最后，让 GPT 帮你把所有指令整理成一份表格：

Copy
请帮我把刚才设定的主指令，一级指令，二级指令，以及它们的功用，整合为一个markdown表格输出给我

它就会输出如下表格：

主指令 | 功用
explain | 对指定的 Rust 代码进行详细的解释
debug | 按给定的编译器错误提示来帮助查找代码 bug 并给出修正代码
work | 输出覆盖 Rust 入门和日常编写的常用特性和知识点的问题
expert | 输出 Rust 专家级特性和知识点的问题
quiz | 输出容易令人迷惑的 Rust 语言特性的题
anwser | 指定应该回答的问题数目，并附上对答案的解释

一级指令 | 功用
-tech | 输出相应主指令级别的概念讲解，并附带示例
-type | 输出相应主指令级别的题目
-idea | 输出对应主指令级别的项目实践想法和思路供人参考
-crate | 只限主指令 work 使用，推荐相应的第三方 crate
-n | 只限主指令 anwser 使用，指定要回答的问题数目，并附解释

二级指令 | 功用
--cn | 以中文来输出内容
--en | 以英文来输出内容
--jp | 以日文来输出内容

“魔法” 系统使用效果#

现在我们的 “魔法” 系统就可以使用了，通过组合主指令、一级指令和二级指令可以达到我们期望的效果。

我们可以用它完成以下功能：

生成不同级别的 Rust 题目和答案，可以考察自己的知识点

可以指定 Rust 语言特性让它生成对应的题目

可以让它推荐练手项目

可以让它回答自己生成的问题

可以指定输出内容的自然语言

生成 work 级别的题目#

Copy
work

GPT-4:

以下是两道工作级别的 Rust 题目，一道选择题和一道编写代码实现的题。

选择题：

在 Rust 中，哪个关键字用于声明一个不可变变量？
A. let
B. mut
C. const
D. static

编写代码实现题：

请编写一个 Rust 函数 find_max，该函数接收一个整数向量作为参数，并返回向量中的最大值。如果向量为空，则返回 None。示例函数签名如下：

Copy
fn find_max(numbers: Vec<i32>) -> Option<i32> {
// 在此编写你的代码
}

生成 work 题目数量#

Copy
work -n 1

GPT-4:

GPT 理解 -n 是需要解释题目的，这个可以通过修改指令进行修改。

以下是一道工作级别的 Rust 题目，一道选择题：

选择题：

在 Rust 中，哪个关键字用于声明一个不可变变量？

A. let B. mut C. const D. static

答案： A. let

解释： 在 Rust 中，使用 let 关键字声明一个不可变变量。不可变意味着变量的值在声明后无法更改。如果需要声明一个可变变量，可以使用 let mut。而 const 和 static 用于声明常量，其中 const 是在编译时确定的常量，static 是在运行时分配内存的全局变量。

按指定内容生成题目#

Copy
work -type algorithm

GPT-4:

以下是两道与算法相关的工作级别 Rust 题目，一道选择题和一道编写代码实现的题。

选择题：

在 Rust 中，对于排序算法，以下哪个方法是对向量进行原地排序（即不创建新向量）？

A. sort B. sorted C. sort_by D. sort_unstable

编写代码实现题： 2. 请编写一个 Rust 函数 merge_sorted_arrays，该函数接收两个已排序的整数向量作为参数，并将它们合并为一个新的已排序向量。示例函数签名如下：

Copy
fn merge_sorted_arrays(a: Vec<i32>, b: Vec<i32>) -> Vec<i32> {
// 在此编写你的代码
}

为了防止 GPT-4 生成同样的题目，可以让它换题：

Copy
work -type trait change

更换主指令为 expert

Copy
expert -type trait

GPT-4:

以下是两道与 trait 相关的专业级别 Rust 题目，一道选择题和一道编写代码实现的题。

选择题：

在 Rust 中，关于 trait 对象和静态分发，以下哪个说法是正确的？

A. trait 对象实现了静态分发，性能优于动态分发 B. 静态分发使用 trait 对象，性能优于动态分发 C. 静态分发使用 monomorphization，在编译时确定具体类型，性能优于动态分发 D. 动态分发使用 monomorphization，在编译时确定具体类型，性能优于静态分发

编写代码实现题： 2. 请实现一个名为 Drawable 的 trait，包含一个名为 draw 的方法。然后，请为 Circle 和 Rectangle 结构体实现 Drawable trait。最后，请创建一个包含 Circle 和 Rectangle 的向量，并通过调用 draw 方法遍历绘制它们。示例代码结构如下：

Copy
trait Drawable {
// 在此编写你的代码
}

struct Circle {
// 在此编写你的代码
}

struct Rectangle {
// 在此编写你的代码
}

// 在此为 Circle 和 Rectangle 实现 Drawable trait

fn main() {
// 在此创建 Drawable 向量，并通过调用 draw 方法遍历绘制它们
}

指定只出 “编码实现” 的题目：

Copy
work -n 1 -type ffi 编码实现

以下是一道与 FFI（Foreign Function Interface）相关的工作级别 Rust 题目，为您提供一道编写代码实现的题以及答案和解释。

编写代码实现题：

请使用 Rust 的 FFI 功能，编写一个名为 rust_add 的函数，该函数将两个 C 语言的 int 类型整数相加，并返回结果。请为此函数提供相应的 C 语言函数声明。示例代码结构如下：

Copy
// 在此编写你的 Rust 代码

/*
// 在此提供 C 语言函数声明
extern "C" {
// ...
}
*/

fn main() {
// 在此编写测试代码
}

答案：

Copy
// Rust 代码
#[no_mangle]
pub extern "C" fn rust_add(a: i32, b: i32) -> i32 {
a + b
}

/*
// C 语言函数声明
#include <stdint.h>

extern int32_t rust_add(int32_t a, int32_t b);
*/

fn main() {
// 此处无法直接测试，需要在 C 语言环境中编译并调用
}

解释：首先，我们在 Rust 代码中定义了一个名为 rust_add 的函数，该函数接受两个 i32 类型的参数，并返回它们的和。为了让 C 语言可以调用这个函数，我们需要使用 #[no_mangle] 属性来禁止编译器对函数名进行改写。然后，我们使用 pub extern "C" 关键字指定这个函数的 ABI（Application Binary Interface）为 C 语言风格。

接下来，我们提供了 C 语言函数声明。请注意，C 语言中的 int 类型在不同的平台和编译器上可能具有不同的大小，所以我们在 C 语言中使用 int32_t 类型来确保与 Rust 中的 i32 类型相匹配。这需要包含 stdint.h 头文件。

在 main 函数中，我们无法直接测试这个 FFI 函数，因为需要在 C 语言环境中编译并调用。您可以创建一个简单的 C 语言程序，将 Rust 编译为动态库或静态库，并链接到 C 语言程序中，然后调用 rust_add 函数进行测试。

我没搞清楚，它为什么有时候要带解释和答案。

推荐练手项目#

Copy
work -idea

GPT-4:

以下是一些 Rust 工作级别的项目实践想法和思路供您参考：

命令行工具：创建一个用于处理日常任务的命令行工具，例如批量重命名文件、下载网络资源或者解析日志文件。可以使用 clap 或 structopt 库来处理命令行参数。

Web 服务器：使用 Rust 编写一个简单的多线程 Web 服务器，用于处理 HTTP 请求并提供静态或动态内容。可以使用 hyper 和 tokio 库来实现异步 I/O 和 HTTP 功能。

网络爬虫：编写一个网络爬虫，从指定的网站抓取内容并解析出所需信息。可以使用 reqwest 库来处理网络请求，select.rs 库来解析 HTML 文档。

聊天服务器和客户端：创建一个聊天服务器和客户端，允许多个用户通过 TCP 连接进行实时通信。可以使用 tokio 库来实现异步 I/O 和多线程功能。

文件加密工具：实现一个简单的文件加密和解密工具，用于保护用户的隐私数据。可以使用 rust-crypto 或 ring 库来实现加密算法。

文本编辑器：开发一个简单的文本编辑器，支持基本的文本编辑功能，如打开、保存、查找和替换。可以使用 crossterm 或 termion 库来实现跨平台的终端界面。

可以指定 idea 关键字：

Copy
work -idea cli gpt

GPT-4:

以下是一个使用 Rust 开发的命令行界面 (CLI) GPT 工具的项目实践想法和思路供您参考：

项目名称：GPT-CLI

项目概述：创建一个命令行界面 (CLI) 工具，允许用户通过输入文本来与 GPT 模型进行交互，并获得模型生成的文本。这个工具可以用于多种用途，如写作辅助、编程问题解答、自动回复邮件等。

关键功能：

文本输入：允许用户输入文本，以便将其作为 GPT 模型的输入。

GPT 模型集成：集成一个 GPT 模型（例如 OpenAI 的 GPT-3 或自行训练的模型），并调用其 API 来生成基于用户输入的文本。

结果展示：将 GPT 模型生成的文本结果显示给用户。

保存功能：允许用户将生成的文本结果保存到指定的文件中。

库和工具建议：

命令行参数处理：使用 clap 或 structopt 库来处理命令行参数。

HTTP 请求库：使用 reqwest 库来处理与 GPT 模型 API 的网络请求。

异步编程：使用 tokio 或 async-std 库来实现异步编程，提高性能。

环境变量管理：使用 dotenv 库来管理 API 密钥等敏感信息。

开发步骤：

设计命令行参数结构，如输入文本、保存文件路径等。

使用 clap 或 structopt 库解析命令行参数。

集成 GPT 模型 API，如 OpenAI GPT-3，设置 API 密钥和请求参数。

使用 reqwest 库发送 HTTP 请求，获取 GPT 模型生成的文本结果。

处理 GPT 模型返回的结果，并将其显示给用户。

实现将生成的文本保存到文件的功能。

对程序进行测试和优化。

回答自己生成的问题#

Copy
expert -type struct

GPT-4:

以下是两道与结构体相关的专业级别 Rust 题目，一道选择题和一道编写代码实现的题。

选择题：

在 Rust 中，关于结构体派生 PartialEq 和 Eq trait 的说法，哪个是正确的？

A. 所有结构体都必须实现 PartialEq 和 Eq trait B. 结构体只需实现 PartialEq trait，Eq trait 会自动派生 C. 结构体可以选择派生 PartialEq 和 Eq trait，但前提是其所有字段都实现了这些 trait D. 结构体派生 PartialEq 和 Eq trait 时，不需要考虑其字段的 trait 实现

编写代码实现题： 2. 请创建一个名为 Color 的结构体，包含三个 u8 类型的字段：red、green 和 blue。为 Color 结构体实现 PartialEq trait，比较两个 Color 实例是否相等。两个 Color 实例相等的条件是：它们的 red、green 和 blue 字段值之差都小于等于 10。示例代码结构如下：

Copy
use std::cmp::PartialEq;

struct Color {
// 在此编写你的代码
}

impl PartialEq for Color {
// 在此编写你的代码
}

fn main() {
// 在此创建两个 Color 实例，并比较它们是否相等
}

Copy
anwser -n 2

根据您的要求，我将为您提供前两个问题的答案及解释。

选择题答案及解释：

在 Rust 中，关于结构体派生 PartialEq 和 Eq trait 的说法，哪个是正确的？

答案：C. 结构体可以选择派生 PartialEq 和 Eq trait，但前提是其所有字段都实现了这些 trait

解释：在 Rust 中，结构体可以选择性地为其派生 PartialEq 和 Eq trait，这允许您对结构体实例进行相等性比较。然而，派生这些 trait 的前提是结构体的所有字段都已经实现了这些 trait。如果结构体的某个字段没有实现这些 trait，您将无法为该结构体派生 PartialEq 和 Eq trait。

编写代码实现题答案及解释： 2. 请创建一个名为 Color 的结构体，包含三个 u8 类型的字段：red、green 和 blue。为 Color 结构体实现 PartialEq trait，比较两个 Color 实例是否相等。两个 Color 实例相等的条件是：它们的 red、green 和 blue 字段值之差都小于等于 10。示例代码结构如下：

答案：

Copy
use std::cmp::PartialEq;

struct Color {
red: u8,
green: u8,
blue: u8,
}

impl PartialEq for Color {
fn eq(&self, other: &Self) -> bool {
(self.red as i32 - other.red as i32).abs() <= 10 &&
(self.green as i32 - other.green as i32).abs() <= 10 &&
(self.blue as i32 - other.blue as i32).abs() <= 10
}
}

fn main() {
let color1 = Color { red: 255, green: 128, blue: 64 };
let color2 = Color { red: 250, green: 130, blue: 70 };

println!("Color1 equals Color2: {}", color1 == color2);
}

解释：首先，我们定义了 Color 结构体，并为其添加了三个 u8 类型的字段：red、green 和 blue。接下来，我们为 Color 结构体实现了 PartialEq trait，并定义了 eq 方法。在 eq 方法中，我们比较了两个 Color 实例的 red、green 和 blue 字段值之差的绝对值是否小于等于 10。如果满足条件，那么这两个 Color 实例被认为是相等的。最后，在 main 函数中，我们创建了两个 Color 实例，并使用 == 运算符比较它们是否相等。

推荐 crate#

Copy
expert -crate eBPF

GPT-4:

好的，以下是针对 eBPF 主题的专业级别题目以及推荐的第三方 crate：

题目：

请详细介绍一下 eBPF 技术在 Rust 中的原理以及应用场景。

在 Rust 中，如何使用 libbpf-rs crate 编写高效的 eBPF 程序？

在 Rust 中，如何使用 redbpf-probes crate 实现自定义的 eBPF 事件探针？

第三方 crate 推荐：

libbpf-rs：用于编写和加载 eBPF 程序的 Rust crate。

redbpf-probes：用于编写自定义 eBPF 事件探针的 Rust crate。

注意：eBPF 技术需要在支持它的内核上运行。如果您的系统不支持 eBPF，那么您可能无法完成此任务。

可以指定输出内容的自然语言#

要求它用日语输出

Copy
work -tech lifetime --jp

GPT-4:

以下は、ライフタイムに関連する技術概念の説明と例を日本語で提供します。

ライフタイムとは、Rust でメモリ管理や参照の安全性を担保するための概念です。ライフタイムは、コンパイル時に参照の有効性を検証し、参照されるオブジェクトが参照されている期間中に破棄されないことを保証します。

例:

Copy
fn longest<'a>(s1: &'a str, s2: &'a str) -> &'a str {
if s1.len() > s2.len() {
s1
} else {
s2
}
}

fn main() {
let s1 = String::from("長い文字列");
let s2 = String::from("短い");

let result = longest(&s1, &s2);
println!("最も長い文字列は: {}", result);
}

上記の例では、longest関数は 2 つの文字列のスライスを引数に取り、最も長いものを返します。ライフタイムパラメータ'aは、入力参照と戻り値の参照が同じライフタイムを持つことを示しています。

longest関数は、参照されるオブジェクトが参照されている期間中に破棄されないことを保証するために、ライフタイムを使用しています。これにより、ダングリングポインタや他のメモリ安全性に関連する問題を防止できます。

要求它用英文输出：

Copy
work -tech --en lifetime

二级指令的位置不影响它理解

Below is an explanation and example of the technical concept of lifetimes in Rust, provided in English.

Lifetimes are a concept in Rust for managing memory and ensuring reference safety. Lifetimes are used to verify the validity of references at compile-time, ensuring that the object being referenced is not destroyed while it is still being referenced.

Example:

Copy
fn longest<'a>(s1: &'a str, s2: &'a str) -> &'a str {
if s1.len() > s2.len() {
s1
} else {
s2
}
}

fn main() {
let s1 = String::from("long string");
let s2 = String::from("short");

let result = longest(&s1, &s2);
println!("The longest string is: {}", result);
}

In the example above, the longest function takes two string slices as arguments and returns the longer one. The lifetime parameter 'a indicates that the input references and the returned reference have the same lifetime.

The longest function uses lifetimes to ensure that the object being referenced is not destroyed while it is still being referenced, preventing dangling pointers and other memory safety-related issues.

小结#

这套 “魔法” 学习系统，设定起来并不困难，但是对日常学习 Rust 的帮助应该是巨大的。最后，希望大家能把你们自己扩展的好用的指令，或者值得记录的 GPT-4 生成的 Rust 问题和解答都分享到 RustChat 的 GitHub 仓库，形成一个问答知识库来帮助更多的 Rust 学习者。

感谢阅读。