Files
gcs-nf/.agents/rule/编码规范.md
T
hm e7cf44504c feat: Service Registry + Bridge 解耦架构 + 全工程代码清理
## 架构升级:Service Registry + Bridge 模式

- 新增 PluginSDK/IPluginServices.h:10 个纯虚服务接口(IDataProvider/ILinkProvider/...)
- 新增 MavLinkServiceBridge:单 QObject 实现全部服务,隔离 MavLinkNode 依赖
- 升级 PluginManifest:支持 plugin.json 的 provides/consumes 声明式依赖
- 实现 ExtensionHost::autoWire():元对象自省自动连接信号槽
- 集成到 AppController:initModules() 中创建桥接器并注册到 ServiceRegistry
- CockpitPlugin 演示服务发现:initialize() 中通过 PluginContext 查找服务

## 代码清理

- Plugins/opmap:~280 行死代码(waypointsetting 100行注释块/tilematrix 54行/等27个文件)
- Plugins/MavLinkNode:~200 行 GBK 乱码注释翻译为 UTF-8 + 12 行注释死代码
- Plugins/ToolsUI:~222 行死代码(ECU.cpp 82行/INS.cpp 113行/Parse/ToolsUI 等)
- StatusUI/Setting/MissionUI:~65 行注释死代码
- Cockpit/leftladder.cpp:10 处 GBK 乱码翻译为中文
- 清理头文件注释掉的 #include(19 处)、空 if-else 分支、注释变量声明

## 编译验证

- [100%] Built target GCS 零错误
- 运行时 timeout 3s 正常退出,无崩溃
2026-06-01 09:46:36 +08:00

801 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# C/C++ 编码规范
> **适用项目**: GCS 地面控制站
> **语言标准**: C++17
> **框架**: Qt 5/6
> **构建工具**: CMake 3.16+
> **格式化配置**: `.clang-format` (根目录)
> **编码格式**: UTF-8
---
## 1. 总则
### 1.1 核心原则
| 原则 | 说明 |
|------|------|
| **一致性优先** | 新增/修改的代码必须与所在文件或模块的现有风格保持一致 |
| **最小改动** | 仅修改目标代码,不随意改写无关代码、不随意添加无关注释 |
| **编译安全** | 修改后必须能通过编译;增删成员时检查所有引用 |
| **头文件自足** | 每个 `.h` 应包含其所有依赖的前向声明或 include |
### 1.2 编码原则
- 代码优先考虑**可读性**和**可维护性**
- 避免过度抽象,优先使用简单方案
- 所有面向用户的字符串必须使用 `tr()` 包裹
- 禁止在新代码中使用裸 `new` / `delete` 管理资源(除非 Qt 父子关系托管的 QObject 构造)
---
## 2. 文件组织
### 2.1 文件命名
| 类型 | 规范 | 示例 |
|------|------|------|
| 源文件 | `PascalCase.cpp` | `DataLink.cpp` |
| 头文件 | `PascalCase.h` | `DataLink.h` |
| UI 文件 | `PascalCase.ui` | `StatusUI.ui` |
| 资源文件 | `snake_case.qrc` | `resources.qrc` |
### 2.2 目录结构
**当前项目实际结构**
```
gcs_nf/
├── App/ # 主程序入口 + MainWindow + AppController
├── PluginSDK/ # 插件开发包(静态库)
├── Plugins/ # 所有功能插件(每个插件一个目录)
│ ├── IPlugin.h # 插件接口 v2
│ ├── IPlugin_v3.h # 插件接口 v3(向后兼容 v2)
│ ├── Cockpit/ # 自绘飞行仪表盘
│ ├── MavLinkNode/ # MAVLink 协议解析
│ ├── dlink/ # 数据链路层
│ ├── opmap/ # 离线地图引擎
│ ├── Skin/ # 主题/皮肤
│ ├── Setting/ # 系统设置
│ └── ...
├── mavlink/ # MAVLink C 库(git submodule
├── docs/ # 文档
├── bin/ # 构建产物输出目录
└── .clang-format # 代码格式化配置
```
**文件组织约定**
- 每个插件目录下至少有一个与插件同名的 `.h` / `.cpp` 主文件
- 使用 `inc/` / `src/` 分离头文件和源文件的模块(如 Cockpit、MavLinkNode),新增文件也遵循此布局
- UI 文件与使用它的类放在同一目录
- 插件目录下必须有 `plugin.json` 清单文件
### 2.3 Include 顺序
遵循 **Google C++ Style** 的 include 顺序,各组之间空一行分隔:
```
1. 自身对应的头文件(仅 .cpp 中): #include "MyClass.h"
2. C 系统头文件: #include <cstdint>
3. C++ 标准库头文件: #include <memory>
4. 第三方库头文件 (Qt): #include <QObject>
5. 本项目头文件: #include "PluginSDK/PluginContext.h"
```
**示范**`.cpp` 文件):
```cpp
#include "PluginContext.h"
#include <cstdint>
#include <memory>
#include <QDebug>
#include <QObject>
#include "ServiceRegistry.h"
```
**示范**`.h` 文件):
```cpp
#ifndef PLUGINCONTEXT_H
#define PLUGINCONTEXT_H
#include <QObject>
#include "MavLinkService.h"
```
**禁止事项**
- **禁止** 用 `""` 包含系统头文件或 Qt 头文件(如 `#include "QObject"`),一律使用 `<>`
- **禁止** 在头文件中包含不必要的头文件,优先使用前向声明
---
## 3. 命名规范
### 3.1 命名总表
| 元素 | 规范 | 示例 |
|------|------|------|
| 类名 / 结构体名 | `PascalCase` | `MavLinkNode`, `PluginContext` |
| 函数 / 方法 | `camelCase` | `setPortName()`, `onDataReceived()` |
| 成员变量 | `m_camelCase` | `m_portName`, `m_isConnected` |
| 静态常量 / 枚举值 | `kPascalCase``UPPER_SNAKE` | `kDefaultBaud``DEFAULT_TIMEOUT` |
| 局部变量 | `camelCase` | `portName`, `resultCount` |
| 全局变量 | `g_camelCase` | `g_appConfig` |
| 命名空间 | `snake_case` | `mapcontrol`, `plugin_utils` |
| 宏 | `UPPER_SNAKE` | `MAVLINK_START_UART_PORT` |
| 信号 (signal) | camelCase(过去式/完成态) | `dataReceived`, `connectionStateChanged` |
| 槽 (slot) | `on` + 过去式 | `onDataReceived`, `onStartClicked` |
| 枚举类型 | `PascalCase` | `ConnectionState` |
| 头文件守卫宏 | `FILENAME_H` | `MAVLINKSERVICE_H` |
### 3.2 命名细则
**类名**
```cpp
class MavLinkNode : public QObject { }; // 正确 - PascalCase
class mavlink_node : public QObject { }; // 错误 - snake_case
```
**成员变量**
```cpp
private:
QString m_portName; // 正确 - m_camelCase
bool m_isConnected = false; // 正确
QString portName; // 错误 - 缺少 m_ 前缀
```
**信号 / 槽**
```cpp
signals:
void dataReceived(const QByteArray &data); // 正确 - 过去式
void connectionStateChanged(bool connected); // 正确 - 过去式
void signal_heartbeat(); // 弃用 - 旧 MavLinkNode 风格,新代码禁止
public slots:
void onDataReceived(const QByteArray &data); // 正确
void onStartClicked(); // 正确
void slot_data_received(); // 错误 - 弃用前缀
```
**枚举**
```cpp
enum class ConnectionState { // 正确 - enum class
Disconnected,
Connecting,
Connected,
Error
};
enum ErrorCode { // 旧风格(已有代码兼容)
ERR_NONE = 0,
ERR_TIMEOUT,
ERR_PARSE
};
```
---
## 4. 格式规范
### 4.1 格式化工具
项目根目录有 `.clang-format`**每次编辑后必须运行**
```bash
clang-format -style=file -i <修改的文件>
```
### 4.2 核心格式规则
`.clang-format` 提取:
| 规则 | 值 |
|------|-----|
| 基础风格 | LLVM |
| 缩进宽度 | 4 空格 |
| Tab | 禁用(一律空格) |
| 行宽上限 | 120 字符 |
| 大括号风格 | **Allman**(大括号独立一行) |
| 指针/引用对齐 | **靠左** (`int *p`, `QString &s`) |
| 访问修饰符缩进 | -4`public:` / `private:` 向外凸出) |
| include 排序 | **不自动排序**(手动管理) |
| 最大连续空行 | 1 行 |
| 短函数单行 | 允许(如空构造/析构) |
### 4.3 Allman 大括号示范
```cpp
void MyClass::doWork()
{
if (condition)
{
// ...
}
else
{
// ...
}
}
class MyClass : public QObject
{
Q_OBJECT
public:
explicit MyClass(QObject *parent = nullptr);
~MyClass() override = default;
signals:
void finished();
private:
int m_count = 0;
};
```
### 4.4 空行与空格
- 类内各组之间空一行:`public:` / `signals:` / `public slots:` / `private:` 之间
- 函数体之间空一行
- 逻辑相关代码块可以内部不空行,不同逻辑块之间空一行
- `if` / `for` / `while` 关键字后跟一个空格再跟 `(`
---
## 5. 注释规范
### 5.1 注释语言
- 代码注释使用**中文**
- 面向用户的字符串使用 `tr()` 包裹中文
### 5.2 注释格式
**类/结构体注释** — 使用 `/* */``//` 块注释(重要类使用多行):
```cpp
// ============================================================
// AppController — 集中管理所有模块间的信号路由
// ============================================================
class AppController : public QObject
{
Q_OBJECT
// ...
};
```
**函数注释** — 对公开接口和复杂逻辑的函数使用 Doxygen 风格:
```cpp
/**
* @brief 将伺服器 PWM 值转换为角度
* @param pwm 输入的 PWM 值
* @param offset 修正偏移
* @return 转换后的角度值(度)
*/
double pwmToDeg(int pwm, double offset);
```
**内联注释** — 对关键算法或非直观逻辑使用行尾注释:
```cpp
painter.translate(width() * H_pos / 100, height() * V_pos / 100); /* 坐标变换为窗体中心 */
int side = qMin(width(), height()); /* 决定了模块只能是方形 */
```
### 5.3 注释禁止事项
- **禁止** 保留被注释掉的"死代码"(`// old code ...`)。提交前必须删除
- **禁止** 中文内容出现乱码(确保文件保存为 UTF-8 编码)
- **禁止** 注释写"废话"(如 `// i 自增 1``i++` 的注释)
---
## 6. 头文件规范
### 6.1 头文件守卫
**统一使用 `#ifndef` / `#define` / `#endif`**,宏名使用 `文件名大写_H` 格式,结尾带宏名注释:
```cpp
#ifndef PLUGINCONTEXT_H
#define PLUGINCONTEXT_H
// ... 头文件内容 ...
#endif // PLUGINCONTEXT_H
```
**禁止使用 `#pragma once`**(与项目现有代码保持一致)。
### 6.2 前向声明
`.h` 中,如果只需要类型的指针或引用,优先使用前向声明而非 include:
```cpp
// MyPlugin.h
class QAction;
class QWidget;
namespace Ui { class MyPlugin; }
namespace mapcontrol { class OPMapWidget; }
class Config;
```
### 6.3 头文件内容顺序
```cpp
#ifndef MYCLASS_H
#define MYCLASS_H
// 1. 必要的 Qt 头文件
#include <QObject>
// 2. 前向声明
class Config;
namespace Ui { class MyClass; }
// 3. 类定义
class MyClass : public QObject
{
Q_OBJECT
public:
// 构造/析构
// 公开方法
signals:
// 信号
public slots:
// 公开槽
protected:
// 保护成员
private:
// 私有成员
};
#endif // MYCLASS_H
```
---
## 7. 类设计规范
### 7.1 类成员可见性
**从上到下**的顺序:
```
public: 构造/析构 → 公开方法
signals: Qt 信号
public slots: Qt 公开槽
protected: 保护成员(含虚函数)
private: 私有成员变量和方法
```
### 7.2 构造与析构
**使用 `explicit` 修饰单参数构造函数**
```cpp
class PluginContext : public QObject
{
Q_OBJECT
public:
explicit PluginContext(const QString &pluginId, QObject *parent = nullptr);
~PluginContext() override = default;
};
```
- QObject 子类使用 `QObject *parent` 作为最后一个参数,利用 Qt 父子内存管理
- 成员变量**就地初始化**(C++11 语法):
```cpp
private:
QString m_pluginId; // 不需要就地初始化(QString 默认已空)
ServiceRegistry *m_serviceRegistry = nullptr;
MavLinkService *m_mavlinkService = nullptr;
bool m_isConnected = false;
int m_retryCount = 3;
```
- 析构函数中手动 delete 非 QObject 子类的裸指针,并设为 nullptr
- 使用 `Q_UNUSED()` 标记未使用的参数:
```cpp
void MainWindow::resizeEvent(QResizeEvent *event)
{
Q_UNUSED(event)
// ...
}
```
### 7.3 虚函数重写
**重写虚函数必须加 `override` 关键字**
```cpp
class MyPlugin : public IPlugin
{
public:
QString name() const override;
bool initialize(PluginContext *context) override;
void shutdown() override;
protected:
void paintEvent(QPaintEvent *event) override; // QWidget 虚函数
};
```
### 7.4 UI 文件使用
当类使用 `.ui` 文件时:
```cpp
// StatusUI.h
namespace Ui { class StatusUI; }
class StatusUI : public QWidget
{
Q_OBJECT
public:
explicit StatusUI(QWidget *parent = nullptr);
~StatusUI() override;
private:
Ui::StatusUI *ui = nullptr; // 使用 m_ 前缀例外:此处用 ui
};
// StatusUI.cpp
#include "StatusUI.h"
#include "ui_StatusUI.h"
StatusUI::StatusUI(QWidget *parent)
: QWidget(parent)
, ui(new Ui::StatusUI)
{
ui->setupUi(this);
QFile file(":/qss/StatusUI.qss");
file.open(QFile::ReadOnly);
QTextStream filetext(&file);
QString stylesheet = filetext.readAll();
setStyleSheet(stylesheet);
file.close();
}
StatusUI::~StatusUI()
{
delete ui;
}
```
---
## 8. Qt 特定规范
### 8.1 Signal / Slot 连接方式
**新代码优先使用函数指针语法**
```cpp
// 推荐 — 编译期检查
connect(m_button, &QPushButton::clicked, this, &MyClass::onButtonClicked);
// 带 lambda
connect(m_timer, &QTimer::timeout, this, [this]() {
updateStatus();
});
// 信号到信号
connect(m_source, &Source::dataReady, m_target, &Target::onDataReady);
```
**旧式 `SIGNAL()` / `SLOT()` 宏语法仅在维护旧代码时沿用,新代码禁止**
```cpp
// 弃用 — 仅维护旧代码时保留
connect(sender, SIGNAL(valueChanged(int)), receiver, SLOT(onValueChanged(int)));
```
### 8.2 QObject 内存管理
- QObject 子类通过 `parent` 参数加入 Qt 对象树,由 Qt 自动负责析构
- 非 QObject 的成员指针需要在析构函数中手动 delete
```cpp
// QObject 子类 — 不需要手动 delete
m_button = new QPushButton(tr("开始"), this); // this 为 parent
// 非 QObject 裸指针 — 必须在析构函数中 delete
MyClass::~MyClass()
{
delete m_rawData;
m_rawData = nullptr;
}
```
### 8.3 QString 使用
- **所有**界面字符串用 `tr()` 包裹:
```cpp
m_label->setText(tr("连接已建立"));
m_button->setToolTip(tr("点击以断开连接"));
```
- 数值格式化使用 `QString::number()`
```cpp
QString value = QString::number(pwm, 'f', 2); // 保留 2 位小数
QString hex = QString::number(address, 16).toUpper(); // 十六进制大写
```
- 多段拼接使用 `arg()``QStringLiteral`
```cpp
QString msg = tr("端口: %1, 波特率: %2").arg(portName).arg(baudRate);
```
- **禁止** 在界面代码中使用 `std::string`,除非是纯算法库内部
### 8.4 自绘组件(无 .ui 文件)
对于 Cockpit 等纯自绘组件,遵循以下模式:
```cpp
// Cockpit.h
class Cockpit : public QWidget
{
Q_OBJECT
protected:
void paintEvent(QPaintEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
private:
QColor m_groundColor = QColor(100, 100, 100);
qreal m_roll = 0.0;
};
// Cockpit.cpp
void Cockpit::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event)
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
// ... 绘制逻辑
}
```
### 8.5 qmake `.pro` / `.pri` 兼容
当前项目同时存在 CMake 和 qmake 构建系统。如果修改涉及 `.pro` 文件:
- 保持 `.pri` 文件与 `CMakeLists.txt` 中的源文件列表同步
- 新增文件需同时添加到两个构建文件中
---
## 9. 错误处理与日志
### 9.1 日志等级与使用
```cpp
qDebug() << "调试信息"; // 开发期间输出,发布前删除或注释
qInfo() << "关键操作日志"; // 应保留,用于运行时追踪
qWarning() << "非致命异常"; // 如文件打开失败、配置缺失等
qCritical() << "严重错误"; // 可能导致功能不可用的错误
```
### 9.2 错误处理模式
**不抛出异常**(项目不使用 try/catch/throw):
```cpp
// 检查条件,提前返回
QFile file(path);
if (!file.open(QIODevice::ReadOnly))
{
qWarning() << "无法打开文件:" << path << file.errorString();
return false;
}
// 检查空指针
if (!config)
{
qWarning() << "Config 未初始化";
return;
}
```
```cpp
// JSON 解析错误处理
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError)
{
qWarning() << "JSON 解析失败:" << error.errorString();
return;
}
```
### 9.3 断言
- 对不应发生的逻辑错误使用 `Q_ASSERT`Debug 模式生效,Release 模式移除)
- 对 Release 也需要检查的条件使用普通 if 判断
```cpp
Q_ASSERT(plugin != nullptr);
Q_ASSERT(!m_pluginId.isEmpty());
```
---
## 10. 插件开发规范
### 10.1 插件接口
当前项目同时支持两版插件接口:
- **IPlugin v2** ([IPlugin.h](file:///d:/20_AI/gcs_nf/Plugins/IPlugin.h)) — 所有现有插件使用
- **IPlugin v3** ([IPlugin_v3.h](file:///d:/20_AI/gcs_nf/Plugins/IPlugin_v3.h)) — 基于订阅模式,新功能开发优先使用
### 10.2 新插件模板
```cpp
// MyPlugin.h
#ifndef MYPLUGIN_H
#define MYPLUGIN_H
#include "IPlugin_v3.h"
class MyPlugin : public IPlugin_v3
{
Q_OBJECT
public:
explicit MyPlugin(QObject *parent = nullptr);
~MyPlugin() override = default;
// IPlugin 接口
QString name() const override;
QString version() const override;
bool initialize(PluginContext *context) override;
void shutdown() override;
private:
PluginContext *m_context = nullptr;
};
#endif // MYPLUGIN_H
```
### 10.3 plugin.json 清单
每个插件目录必须有 `plugin.json`
```json
{
"name": "MyPlugin",
"version": "1.0.0",
"description": "插件描述",
"activationEvents": ["onStartup"],
"contributes": {}
}
```
---
## 11. CMake 构建规范
### 11.1 CMake 文件位置
- 顶层 `CMakeLists.txt` — 全局设置 + `add_subdirectory`
- 每个模块目录下的 `CMakeLists.txt` — 模块自身的构建规则
### 11.2 新增文件时的操作
1.`.h` / `.cpp` 文件加入对应模块的 `CMakeLists.txt` 中的 `add_library``add_executable` 源文件列表
2. 如模块有 `.pri` 文件,同步更新
3. 新增 `Q_OBJECT` 类时,无需额外操作(`AUTOMOC` 已开启)
---
## 12. AI 编辑指南
> 本章专门面向 AI 辅助编码场景,指导 AI 如何在此项目中安全地编辑代码。
### 12.1 编辑前的必读步骤
1. **读取目标文件** — 使用 Read 工具读取待编辑文件的完整内容
2. **理解上下文** — 检查该文件用到的所有类型、成员、信号槽
3. **确认风格** — 观察现有代码的命名、缩进、注释风格,严格模仿
4. **检查依赖** — 查看 `#include` 列表,确保新增代码的依赖已包含
### 12.2 编辑操作规范
| 操作 | 规范 |
|------|------|
| 新增类 | 创建一个 `.h` + `.cpp`,遵循类设计规范(第 7 章) |
| 新增成员函数 | 在 `.h` 中声明,在 `.cpp` 中实现;检查是否需要 `override` |
| 新增成员变量 | 使用 `m_camelCase` 命名,放在 `private:` 区,作为最后手段才放 `public:` |
| 新增信号 | 放在 `signals:` 区,使用过去式命名 |
| 新增槽 | 放在 `public slots:``private slots:`,使用 `onXxx` 命名 |
| 修改函数体 | 仅修改目标函数,不随意重构无关代码 |
| 添加 include | 插入到 include 分组中的正确位置 |
| 删除代码 | 彻底删除包括相关注释和空行,不留"死代码" |
### 12.3 编辑后的必做检查
- [ ] 新增/修改的类是否添加了 `Q_OBJECT`
- [ ] 是否使用了 `tr()` 包裹所有用户可见字符串
- [ ] 重写的虚函数是否加了 `override`
- [ ] 单参数构造函数是否加了 `explicit`
- [ ] 是否修复了 `#include` 顺序
- [ ] 是否有匹配的 `#ifndef` / `#define` / `#endif` 守卫(新文件)
- [ ] 修改后是否运行了 `clang-format -style=file -i <文件>`
- [ ] 是否同步更新了 `CMakeLists.txt`
### 12.4 常见编辑场景示范
#### 场景 A:给现有类添加一个槽函数
```
步骤:
1. 读取 MyClass.h,在 public slots: 或 private slots: 区添加声明
2. 读取 MyClass.cpp,在文件末尾添加函数实现
3. 使用 clang-format 格式化
```
#### 场景 B:创建一个新的简单对话框
```
步骤:
1. 在某插件目录下创建 MyDialog.h / MyDialog.cpp / MyDialog.ui
2. MyDialog.h:按照 7.4 节 UI 文件使用规范编写
3. MyDialog.cpp:实现构造/析构函数
4. 同步更新 CMakeLists.txt
5. 使用 clang-format 格式化所有新文件
```
#### 场景 C:在现有文件中添加一个信号
```
步骤:
1. 读取头文件,在 signals: 区添加信号声明
2. 确认信号使用过去式名称(如 dataReady, finished
3. 查找所有用到信号的地方(通过 grep),确认连接方式
```
### 12.5 代码样式速查卡片
```
命名: 类 PascalCase | 成员 m_camelCase | 信号过去式 | 槽 onXxx
格式: Allman 大括号 | 4 空格缩进 | 120 列宽 | 指针靠左 int *p
头文件: #ifndef_H 守卫 | #include <> 系统头 | #include "" 项目头
类结构: public → signals → public slots → protected → private
字符串: tr("中文字符串") | QString::number(x, 'f', 2)
调试: qDebug / qInfo / qWarning | 不用 try/catch
构造: explicit 单参构造 | QObject *parent = nullptr 最后 | 成员就地初始化
重写: 所有虚函数重写加 override
插件: 实现 IPlugin_v3 | 提供 plugin.json | 在 CMakeLists.txt 注册
编辑后: 运行 clang-format | 检查 tr() | 检查 CMakeLists.txt 同步
```
---
## 附录 A:与旧代码风格兼容说明
项目中有大量 2020-2022 年编写的旧代码,其风格与本文规范有所不同。AI 在修改旧文件时:
| 旧风格 | 新风格(本文规范) | 处理方式 |
|--------|-------------------|---------|
| `signal_heartbeat()` | `heartbeatReceived()` | **仅新增信号**用新风格;已有旧信号保持不变 |
| `SIGNAL(...)` / `SLOT(...)` | `&Class::method` | **新增 connect** 用新语法;已有旧连接保持不变 |
| 无 `override` | 添加 `override` | 修改到旧函数时可顺手补上 |
| 无 `explicit` | 添加 `explicit` | 修改到旧类时可顺手补上 |
| `#include "QObject"` | `#include <QObject>` | 修改到 include 时可顺手修正 |
| public 成员变量 | private + m_ 前缀 | **仅新增成员**用新风格;已有 public 成员保持不变 |
| 中文注释乱码 | UTF-8 中文注释 | 修改到乱码注释附近时修正 |