ArduPilot 样式指南

为了提高 ArduPilot 代码的可读性,帮助新用户更快地掌握代码,请使用以下样式。如果不遵守这些样式,提交的代码可能会被拒绝。

备注

由于历史原因,规范的某些部分可能不符合要求,但新增加的部分应该符合要求!

风格 是我们格式化代码的首选工具。虽然我们目前还不能自动格式化代码,但在 Tools/CodeStyle 目录下有一个 astylerc 文件,可用于根据以下指南自动格式化代码。

风格 --选项=工具/代码风格/天体运动 <;文件名>;

请记住,对文件格式的任何更改都应作为单独的提交进行。

案例

变量名和函数名均为小写,以下划线分隔。

输入节流阀

错了

节流阀输入

缩进

空间

所有地方均使用 4 个空格缩进。不要使用制表符。

int 动物()
{
    返回 0;
}

错了

int 动物()
{
  返回 0;
}

案例说明

大小写标签的缩进既可以与开关并列,也可以缩进一次。

开关 (条件) {
    个案 foo_cond:
        动物();
        断裂;
    个案 bar_cond:
        酒吧();
        断裂;
}

开关 (条件) {
个案 foo_cond:
    动物();
    断裂;
个案 bar_cond:
    酒吧();
    断裂;
}

错了

开关 (条件) {
个案 foo_cond:
    动物();
断裂;
个案 bar_cond:
    酒吧();
断裂;
}

间距

一元运算符

单运算符周围不要留空格。

i++;

错了

i ++ ;

控制声明

在控制语句及其括号之间留出空格。

如果 (条件) {
    动物();
}

错了

如果(条件) {
    动物();
}
如果 (条件){
    动物();
}

功能调用

请勿在函数和括号之间或括号和内容之间放置空格。

动物(a, 10);

错了

动物 (a, 10);
动物(a, 10 );

尾部空格

不要在新代码中留下拖尾空白(好的编辑器可以帮你处理这个问题)。修复现有代码中的空白时,应单独提交(不要与其他更改一起提交)。

换行

单项发言

除了头文件中的方法实现可能(也可能不)在一行之外,每条语句都应独立成行。

x++;
y++;
如果 (条件) {
    动物();
}

错了

x++; y++;
如果 (条件) 动物();

bool requires_GPS()  否决 { 返回 错误; }

Else 语句

一个 不然 语句应与前面的封闭括号放在同一行。

如果 (条件) {
    动物();
} 不然 {
    酒吧();
}

错了

如果 (条件) {
    动物();
}
不然 {
    酒吧();
}

牙套

功能括号

函数定义:将每个括号放在单独一行。对于头文件中的方法,括号可以内联。

控制声明

控制语句 (如果, 虽然, , 不然) 应始终在语句周围使用大括号。

如果 (条件) {
    动物();
} 不然 {
    酒吧();
}

错了

如果 (条件)
    动物();
不然
    酒吧();

其他支架

将开口括号放在代码块的前一行;将闭口括号放在它自己的一行。

 我的类 {
    ...
};

命名空间 AP_HAL {
    ...
}

对于 (int i = 0; i <; 10; i++) {
    ...
}

错了

 我的类
{
    ...
};

名称

非官方成员

类中的私有成员前应加上下划线:

 我的类 {
私人:
    int 你的工作是什么?;
};

错了

 我的类 {
私人:
    int 领域;
};

班级名称

班级名称的每个单词都应大写,并用下划线分隔。

 AP_Compass { };

错了

 ap_compass { };

枚举

首选枚举类而不是原始枚举。枚举应使用 PascalCase,并且是单数。所有条目都应有逗号,即使是最后一条。

枚举  指南针类型 {
    FOO,
    酒吧,
};

错了

枚举 罗盘类型 {
    FOO,
    酒吧
};

函数和变量

返回单个物理值的函数或表示物理值的变量应以物理单位作为后缀。

uint16 get_angle_rad() { ... };
浮动 距离_m;

错了

uint16 获取角度() { ... };
浮动 距离;

表示相对于框架的值的函数或变量,应首先以框架为后缀,然后以物理单位(如有)为后缀。

uint16 获取标定的厘米距离() { ... };
uint16 获取距离菜单() { ... };
浮动 位置_neu_mm;

错了

uint16 获取距离() { ... };
浮动 位置;

评论

每个具有公共可见性的文件、函数和方法都应在顶部添加注释,说明其作用。

参数

用户可以从这些字段中收集关键信息。有了代码中详细记录的参数,维基和 GCS 就能自动更新参数。如果对参数进行了适当的记录,用户通常就可以对载具进行调校,而不需要成页成页的外部非链接文档。虽然这里的信息不能替代调校指南,但可以非常有效地指导用户更改正确的参数。

包含多个单词的参数应按重要性从左至右排列单词:

  • 飞行模式、功能或传感器应是第一个词。例如,仅与 RTL 飞行模式相关的参数应以 "RTL "开头,如 "RTL_ALT"。

  • MIN"、"MAX "或单位(极少数情况下它们会出现在名称中)等限定符应放在最右边。例如,RTL_ALT_MIN 比 RTL_MIN_ALT 好。

尽可能重复使用其他参数中的词,而不是创建新词。例如,我们使用 "MIN"(最小值)和 "MAX"(最大值),因此应使用这两个词,而不是 "TOP"(上)和 "BOTTOM"(下)这样的对等词。

参数应以标准单位表示(距离以米为单位,角度以度为单位),但如果参数不是以标准单位表示,则可以(选择性地)在末尾添加单位。这绝对不是要求,而是由开发者决定的。

参数名称的总长度必须小于或等于 16 个字符。

显示名称

显示名称通常是一个 2-5 个字的短语,用来描述参数的变化。通常是参数名称的全称。不要以 "the "等非描述性词语开头。PTCH_LIM_MAX_DEG 的一个好的显示名称是 "最大螺距角"。

说明

描述是一个长文本字段,用于完整描述参数以及更改参数对载具行为的影响。描述应简明扼要,同时向用户提供最关键的信息。

// @描述: 收益 附加的  沥青  保持 飞机  降序  升序  匝数. 增加  增量  0.05  减小 高度 损失. 减少 对于 高度 收获.

错了

// @描述:    收获 学期   应用   沥青 费率 胶印 计算 作为 所需  保持  鼻子 夷为平地 期间 匝数. "这款" 默认 价值  1 其中  工作 对于 一应俱全 模型. 高级 用户  使用   正确的 对于 高度 变异  匝数. 如果 高度  失去的 当初  匝数    增加   增量  0.05  赔偿. 如果 高度  获得 当初  匝数     减少.

避免在描述中出现:

  • 所有参数通用的帮助性词语和非描述性语言,如 "本参数可改变......、您等"。

  • 引用其他参数,除非是关键参数

  • 将 0 设置描述为 "禁用"

  • 默认设置

在描述中鼓励:

  • 现在时语言

  • 更改参数的后果(这也为用户如何调整载具提供了指导)

  • 使用或忽略参数时

  • 当 0 设置使用另一个参数值时

值、单位、范围

数值、单位、范围和步骤对于调整参数也至关重要。尽可能将其包括在内。

用户

用户字段有助于对高级参数进行分类和隐藏,防止新用户对其进行调整。目前有 2 个用户字段:

  • 标准 - 任何人都可使用

  • 高级 - 供高级用户使用

浮点注释

ArduPilot 使用 -单精度常数.

这意味着目前允许在常量中省略 float 指定符。同时,也允许有浮点符。

1.0f
1.0

乘法与除法

尽可能使用乘法而不是除法:

 浮动 foo_m = foo_cm * 0.01;

错误

 浮动 foo_m = foo_cm / 100;

乘法的计算周期通常比除法少。

ArduPilot 设计决策

为了适应 ArduPilot 的嵌入式特性,ArduPilot 代码库做出了几项设计决定,这可能会让一些程序员感到惊讶。

内存隐式清零

内存的隐式归零可以让我们的行为更加一致(即使是不好的行为),还可以节省闪存空间,因为代码中的大多数地方都不需要初始化它们已经分配的内存。唯一必须清零的内存是堆栈存储变量 - locals、asprintf 等。

  • new 和 malloc 都将内存清零

  • bss 存储的数据无需清零(因此单例对象中的成员无需清零

  • 向量是特殊的,即使在堆栈中也是零

  • 函数中的静态变量(我们通常不赞成)无需清零

比特字段通常不受欢迎

使用位字段可以减少 RAM 的使用量,但会大大增加闪存的使用量,因为从位字段中提取布尔真值需要更多的机器指令。如果变量被频繁访问,闪存的使用量就会很大。

非首选:

 Foo() {
私人:
    bool 应飞  : 1;
    bool should_grow : 1;
};

首选:

 Foo() {
私人:
    bool 应飞;
    bool should_grow;
};

在头文件中而不是在构造函数中初始化成员变量

如果成员不依赖于构造函数参数,我们更倾向于在类定义中进行初始化。

非首选:

Foo::Foo() :
  酒吧(37),
  baz(BAZ_DEFAULT)
{
  ...
}

首选:

 Foo() {
:
...
私人:
  uint8_t 酒吧 = 37;
  浮动 baz = BAZ_DEFAULT;
};

无标准图书馆

出于提高效率的考虑,ArduPilot 不使用 C 标准库 (std::).我们还倾向于使用跨平台一致的函数,以简化支持(例如,在支持 64 位数学的平台上不使用 64 位数学就很有用)。

这意味着没有 std::vector没有 std::string 没有 std::unordered_map例如

我们尽量避免使用库调用来处理自己的分配,但如果你真的想这样做,仅仅包含获取这些分配的头文件是不够的,你还需要在构建系统中做手脚,以链接 标准 中。

替代品 std::vector

大多数情况下使用固定长度的数组。

AP_ExpandingArray 这也许是您的一个选择--但在飞行中扩展可能是件坏事。

有些地方使用链接列表。

替代品 std::string

asprintf 在某些地方使用。一般来说,只需使用 char* 就足够了。

替代品 std::unordered_map

创建结构数组并迭代。例如,在将 mavlink id 转换为 ap_message id 时就需要这样做。

创建一个结构体有序数组,并对其进行二叉搜索。

创建一个具有完美哈希值的结构数组。

无死亡代码

我们不会在 ArduPilot 中保留无效代码。如果代码未被使用,则应将其删除,而不仅仅是注释掉。这是一般规则,并非普遍遵守。