C/C++ 各种代码风格的对比和总结
Untitled10032
·
2023-04-09 11:11:02
·
个人记录
前言
各位在编程的时候,大概都多多少少遇到过“怎么写好看”、“怎么写方便”的代码风格相关的问题。本文总结对比一些知名度较高(且文档友善、易于查看)的代码风格的特点与异同,以便大家参考。
本文中只总结对比 OI 中常用的代码风格细节,如果想要更深入了解可以点击下方链接详细阅读。文中每段话的后面都会附上原文本相应段落的链接,以便大家查看。
目录
代码风格汇总
头文件引用
using 与 using namespace std;
命名规则
类型命名规则
变量命名规则
函数命名规则
其它命名规则
命名规则汇总表格
大括号换行问题
Tab 还是 空格?
缩进宽度
哪里需要有空格?
总结
各种代码风格的大致印象
代码风格汇总
\text{google} (Google C++ Style Guide)
\text{webkit} (Code Style Guidelines | WebKit)
\text{llvm} (LLVM Coding Standards — LLVM 17.0.0git documentation)
\text{firefox} (C++ Coding style — Firefox Source Docs documentation)
\text{linux} (Linux 内核代码风格 — The Linux Kernel documentation)
_(排名不分先后)_
# 头文件引用
$\text{google}$:需要遵循顺序:C 库头文件,C++ 库头文件,其他库的头文件,自己项目的头文件。且每种类型间应该有一个空行。建议将每个你在程序中用到的功能所在的头文件直接引用,即使其它头文件已经间接引用了这个头文件。[$\tiny\text{link}$](https://google.github.io/styleguide/cppguide.html#Names_and_Order_of_Includes)
$\text{webkit}$:头文件区分大小写排序(好像是字典序)。系统头文件必须写在最后。[$\tiny\text{link}$](https://webkit.org/code-style-guidelines/#include-others)
$\text{llvm}$:需要遵循顺序:主模块头文件,局部或私有头文件,LLVM 项目/子项目头文件,系统头文件。而且应尽可能减少包含头文件以提高编译速率。[$\tiny\text{link}$](https://llvm.org/docs/CodingStandards.html#include-style)
$\text{firefox}$:只 include 自己需要的头文件。有条件(`#ifdef` 等)的头文件应在其它头文件后。[$\tiny\text{link}$](https://firefox-source-docs.mozilla.org/code-quality/coding-style/coding_style_cpp.html#include-directives)
$\text{OIer}$ 常用码风:按头文件名称长度升/降序,或按想到的先后顺序引用头文件,或习惯只引用 `
其它代码风格未发现有提及。
# using 与 using namespace std;
$\text{google}$:不要使用 `using`。[$\tiny\text{link}$](https://google.github.io/styleguide/cppguide.html#Namespaces)
$\text{webkit}$:头文件中除少数情况外不可使用 `using`,C++ 实现文件中不能使用 `using`。[$\tiny\text{link}$](https://webkit.org/code-style-guidelines/#using-in-cpp)
$\text{llvm}$:单独一段标题叫“Do Not Use `using namespace std`”(笑 [$\tiny\text{link}$](https://llvm.org/docs/CodingStandards.html#do-not-use-using-namespace-std)
$\text{firefox}$:`using namespace` 只可以在所有 `#include` 后。不允许在头文件中使用 `using`,除非在类或函数之内。[$\tiny\text{link}$](https://firefox-source-docs.mozilla.org/code-quality/coding-style/coding_style_cpp.html#c-namespaces)
$\text{OIer}$ 常用码风:直接用,确实方便。
其它代码风格未发现有提及。
------------
**所有列出的代码风格(除 OIer 码风)大多数都建议不使用 `using namespace std`(或未明确说明),因为它会引起全局命名空间污染,即自己在程序中定义的东西与库中的函数、变量等重名引发报错的情况。OIer 们也可以尝试一下不 `using namespace std`,如果适应不了那就记得多编译、多检查。**
# 命名规则
$\text{google}$:使用描述对象目的或意图的名称,不要担心太长。减少项目之外的人可能不知道的缩写(类似 `dfs` 这种大部分人都知道的缩写除外)。
而且变量长度应与其出现场合适应,如在一个 5 行的函数中,建议使用 `n` 作为变量名而不是 `total_number_of_foo_errors`,但如果是在一个应用较为广泛的类中,那么更推荐使用更细致的变量名(而不是 `n`)。[$\tiny\text{link}$](https://google.github.io/styleguide/cppguide.html#General_Naming_Rules)
$\text{webkit}$:名称全部使用驼峰。[$\tiny\text{link}$](https://webkit.org/code-style-guidelines/#names-basic)
$\text{llvm}$:使用描述性的变量名称。避免缩写,除非缩写是众所周知的。名称大小写/下划线风格要有一定一致性,避免使用时到处乱找。名称需要使用驼峰。[$\tiny\text{link}$](https://llvm.org/docs/CodingStandards.html#name-types-functions-variables-and-enumerators-properly)
> We cannot stress enough how important it is to use descriptive names. ——LLVM Coding Standards
$\text{linux}$:在 C 语言中提倡使用较为简短的变量名。全局变量需要描述性名称。本地(如函数内)变量名应简短。**linux 内核代码风格中未明确说明各种类型变量的大小写/下划线命名规范,只说不提倡混用大小写,但示例中所有东西的名称(除宏)都是小写 + 下划线风格。**[$\tiny\text{link}$](https://www.kernel.org/doc/html/v4.15/translations/zh_CN/coding-style.html#id5)
## 类型命名规则
_类型指结构体、类等自定义数据类型。_
$\text{google}$:名称使用大驼峰(所有单词首字母大写),不使用下划线。[$\tiny\text{link}$](https://google.github.io/styleguide/cppguide.html#Type_Names)
$\text{webkit}$:大驼峰(所有单词首字母大写)。[$\tiny\text{link}$](https://webkit.org/code-style-guidelines/#names-basic)
$\text{llvm}$:大驼峰。需要是名词。[$\tiny\text{link}$](https://llvm.org/docs/CodingStandards.html#name-types-functions-variables-and-enumerators-properly)
$\text{firefox}$:大驼峰(根据示例代码得出)。[$\tiny\text{link}$](https://firefox-source-docs.mozilla.org/code-quality/coding-style/coding_style_cpp.html#c-classes)
## 变量命名规则
$\text{google}$:名称全部小写,单词间使用下划线分隔。且在一个类中,成员变量名称的末尾需要有一个下划线。结构体中无此要求。[$\tiny\text{link}$](https://google.github.io/styleguide/cppguide.html#Variable_Names)
$\text{webkit}$:小驼峰(第一个单词首字母小写,其它单词首字母大写)。[$\tiny\text{link}$](https://webkit.org/code-style-guidelines/#names-basic)
$\text{llvm}$:大驼峰。需要是名词。[$\tiny\text{link}$](https://llvm.org/docs/CodingStandards.html#name-types-functions-variables-and-enumerators-properly)
$\text{firefox}$:一些前缀规则:[$\tiny\text{link}$](https://firefox-source-docs.mozilla.org/code-quality/coding-style/coding_style_cpp.html#variable-prefixes)
- k:常量
- g:全局变量
- a:参数(原文为 argument,实际参数)
- s:静态成员
- m:成员变量
- e:枚举变量(当声明为 `enum xxx {...}` 时。如声明为 `enum class xxx {...}` 时则遵循枚举类的规则(即大驼峰)。好奇两种写法的差异可 BDFS)。
## 函数命名规则
$\text{google}$:一般使用大驼峰。而赋值和取值函数(如 `get_xxx()` 与 `set_xxx()`)可以仿照变量使用下划线。[$\tiny\text{link}$](https://google.github.io/styleguide/cppguide.html#Function_Names)
$\text{webkit}$:小驼峰(第一个单词首字母小写,其它单词首字母大写)。[$\tiny\text{link}$](https://webkit.org/code-style-guidelines/#names-basic)
$\text{llvm}$:小驼峰。需要是动词短语,表示一个行为。[$\tiny\text{link}$](https://llvm.org/docs/CodingStandards.html#name-types-functions-variables-and-enumerators-properly)
$\text{firefox}$:大驼峰。[$\tiny\text{link}$](https://firefox-source-docs.mozilla.org/code-quality/coding-style/coding_style_cpp.html#methods-and-functions)
## 其它命名规则
$\text{google}$:[$\tiny\text{link}$](https://google.github.io/styleguide/cppguide.html#Constant_Names)
- 常量前需要添加小写 `k`,其余使用大驼峰。
- `namespace` 的名称为小写,单词间下划线分隔。
- 枚举类(`enum`)名称需要使用大驼峰,枚举类中的元素命名规则与常量相同。
- 宏的命名需要全大写,单词间以下换线分隔。
> You're not really going to define a macro, are you? ——Google C++ Style Guide
- 如果被命名的东西与某些已有的其它东西相近,那么可以遵循现有命名规则。如使用 STL 命名风格命名类 `sparse_hash_map`。
$\text{llvm}$:[$\tiny\text{link}$](https://llvm.org/docs/CodingStandards.html#name-types-functions-variables-and-enumerators-properly)
- 枚举类名遵循类型命名规则。
- 枚举类内的元素使用大驼峰命名。
- 特殊地,与 STL相近的类可以遵循 STL 的命名风格,如函数小写、下划线分隔(`push_back()`)。
## 命名规则汇总表格
| | 变量 | 函数 | 类型 |
| :-----------: | :-----------: | :-----------: | :-----------: |
| $\text{google}$ | 下划线 | 大驼峰 | 大驼峰 |
| $\text{webkit}$ | 小驼峰 | 小驼峰 | 大驼峰 |
| $\text{llvm}$ | 大驼峰 | 小驼峰 | 大驼峰 |
| $\text{firefox}$ | - | 大驼峰 | 大驼峰 |
| $\text{linux}$ | 下划线 | 下划线 | 下划线 |
_(如果嫌它挤成了一坨可以到[这个剪贴板](https://www.luogu.com.cn/paste/o7b2jy4k)里看)_
------------
**各种代码规范的命名风格各有千秋,OIer 们可以适度、视情况参考学习。在四五十行就可以解决的程序中,可以为了提升效率使用简短的命名,但如果要写一个几百行的大模拟,那么我不建议你使用 abcd 满天飞的命名风格,而是最好遵循上文中所描述的一些规则。**
# 大括号换行问题
为了方便,将换行风格分为几种:
第一种(前括号都不换行):
```cpp
if (...) {
} else {
}
void foo () {
}
```
第二种(前括号都换行):
```cpp
if (...)
{
}
else
{
}
void foo ()
{
}
```
第三种(if-else 的另一种风格):
```cpp
if (...) {
}
else {
}
```
------------
$\text{google}$:循环与分支语句:第一种。类:示例代码中为第一种。而且要求所有循环和分支语句都要有大括号。[$\tiny\text{link}$](https://google.github.io/styleguide/cppguide.html#Formatting_Looping_Branching)
$\text{webkit}$:循环、分支语句、类:第一种;函数:第二种。[$\tiny\text{link}$](https://webkit.org/code-style-guidelines/#linebreaking-else-braces), [$\tiny\text{link}$](https://webkit.org/code-style-guidelines/#braces)
$\text{linux}$:所有非函数语句块:第一种;函数:第二种(和 $\text{webkit}$ 相近)。[$\tiny\text{link}$](https://www.kernel.org/doc/html/v4.15/translations/zh_CN/coding-style.html#id3)
> 全世界的异端可能会抱怨这个不一致性是... 呃... 不一致的,不过所有思维健全的人 都知道 (a) K&R(注:Linux 风格遵循 K&R 大括号风格)是 正确的 并且 (b) K&R 是正确的。
> —— Linux 内核代码风格(其中对于函数定义时大括号情况与其它不一致的评价)
$\text{OIer}$ 常用码风:第一种和第二种居多,也存在一定的第三种。
------------
**(补充)**:对于 `if`、`for` 等语句后面跟随的代码块中只有一个语句(或没有语句)时,可不可以不打大括号:
- $\text{google}$:不可以,但是如果老代码里这么写了,那就不要改了。
- $\text{webkit}$:可以,而且应该。
- $\text{llvm}$:可以,而且应该。
- $\text{firefox}$:不可以。
- $\text{linux}$:可以,而且应该。
_注:不打大括号的情况有些特例,具体可去上面的那些 link 里查看。_
------------
**第一种风格是最节省行数的一种风格,可以在同屏阅读最多代码;第二种风格好像较为古老,习惯的人会觉得很清晰;第三种风格和第一种差不多。关于大括号换行问题,我个人认为根据习惯选择就行了。**
# Tab 还是 空格?
| | Tab or Space? | link |
| :-----------: | :-----------: | :-----------: |
| $\text{google}$ | space | [link](https://google.github.io/styleguide/cppguide.html#Spaces_vs._Tabs) |
| $\text{webkit}$ | space | [link](https://webkit.org/code-style-guidelines/#indentation-no-tabs) |
| $\text{llvm}$ | space | [link](https://llvm.org/docs/CodingStandards.html#whitespace) |
| $\text{firefox}$ | - | - |
| $\text{linux}$ | tab | [link](https://www.kernel.org/doc/html/v4.15/translations/zh_CN/coding-style.html#id1) |
**$\text{firefox}$ 文档中未明确规定使用 tab 还是空格,但范例代码中的格式化选项中规定为使用空格。**
_[如果嫌表格挤成一坨,来这里](https://www.luogu.com.cn/paste/o7b2jy4k)_
**大多数代码规范都建议使用空格,一个重要原因是各个代码规范都有缩进宽度要求,若使用 Tab 则缩进宽度不固定。VS Code 等一些文本编辑器中,可以设置输入 Tab 时自动转换为空格(好像 VS Code 默认设置就是这样的),所以不用担心敲空格费手的问题。**
# 缩进宽度
| | 缩进宽度 | link |
| :----------: | :----------: | :----------: |
| $\text{google}$ | 2 | [link](https://google.github.io/styleguide/cppguide.html#Spaces_vs._Tabs) |
| $\text{webkit}$ | 4 | [link](https://webkit.org/code-style-guidelines/#indentation-4-spaces) |
| $\text{llvm}$ | - | - |
| $\text{firefox}$ | - | - |
| $\text{linux}$ | 8 | [link](https://www.kernel.org/doc/html/v4.15/translations/zh_CN/coding-style.html#id1) |
| $\text{OIer}$ | 4 | - |
**$\text{llvm}$、$\text{firefox}$ 都未明确说明缩进宽度,但示例代码中缩进宽度都是 2。**
_[如果嫌表格挤成一坨,来这里](https://www.luogu.com.cn/paste/o7b2jy4k)_
# 哪里需要有空格?
- 所有代码规范都要求 `if(...)` 等分支和循环语句与 `{` 之间有一个空格。
- $\text{google}$[$\tiny\text{(link)}$](https://google.github.io/styleguide/cppguide.html#Function_Declarations_and_Definitions)、$\text{webkit}$[$\tiny\text{(link)}$](https://webkit.org/code-style-guidelines/#spacing-function-paren)、$\text{llvm}$[$\tiny\text{(link)}$](https://llvm.org/docs/CodingStandards.html#spaces-before-parentheses) 要求函数的定义、调用时,函数名称与后面跟随的括号间不应有空格。所有代码规范中的范例代码都遵循此条,虽然有些未明确说明。
- 所有代码规范都推荐在单目运算符(`++`、`--`、`sizeof` 等)与参数之间不加空格。
- 所有代码规范都推荐在双目运算符(加减乘除、位运算等)与参数之间加上空格。
**所有代码规范都要求在许多符号与变量之间打上空格,在 OIer 中,不少人的码风是很少打空格的(如 `for(int i=1;i<=n;i++)`),这样可以一定程度地提升编码效率,但可读性有一定下降。我个人习惯敲空格之后倒是觉得编码效率也没下降多少。这方面可根据爱好选择。但如果长大之后从事程序相关的工作,那大概就一定得在代码里多敲点空格了。**
# 总结
## 各种代码风格的大致印象
```cpp
int StringToNumber(char *str, int str_length) {
int result = 0;
if (str[0] == '-') {
for (int i = 1; i < str_length; i++) {
result = result * 10 + str[i] - '0';
}
return -result;
} else {
for (int i = 0; i < str_length; i++) {
result = result * 10 + str[i] - '0';
}
return result;
}
}
//webkit
int stringToNumber(char *str, int strLength)
{
int result = 0;
if (str[0] == '-') {
for (int i = 1; i < strLength; i++)
result = result * 10 + str[i] - '0';
return -result;
} else {
for (int i = 0; i < strLength; i++)
result = result * 10 + str[i] - '0';
return result;
}
}
//llvm
int stringToNumber(char *Str, int StrLength) {
int Result = 0;
if (Str[0] == '-') {
for (int i = 1; i < StrLength; i++)
Result = Result * 10 + Str[i] - '0';
return -Result;
} else {
for (int i = 0; i < StrLength; i++)
Result = Result * 10 + Str[i] - '0';
return Result;
}
}
//firefox
int StringToNumber(char *aStr, int aStrLength) {
int result = 0;
if (aStr[0] == '-') {
for (int i = 1; i < aStrLength; i++) {
result = result * 10 + aStr[i] - '0';
}
return -result;
} else {
for (int i = 0; i < aStrLength; i++) {
result = result * 10 + aStr[i] - '0';
}
return result;
}
}
//linux
int string_to_number(char *str, int str_length)
{
int result = 0;
if (str[0] == '-') {
for (int i = 1; i < str_length; i++)
result = result * 10 + str[i] - '0';
return -result;
} else {
for (int i = 0; i < str_length; i++)
result = result * 10 + str[i] - '0';
return result;
}
}
```
_(代码规范中未明确说明的部分以文档中给出的范例代码为准)_
------------
各种代码风格在许多细节之处有一些不同,但它们的最终目的都是让代码的可读性提升,一个人编写好的代码其他人来都看得懂,这样可以提升团队协作编写代码时的效率、可靠性等。
作为 OIer,我们写的代码给别人看的机会较少,某些情况下只要自己能读懂、语法正确就可以。对于 OIer 来说,适度使用简短的命名可以提升编写效率、某些“不安全”的操作可以提升运行效率……所以不是必须以某种代码风格为标杆,提升自己代码的规范性。况且这些代码规范基本上都是面向生产环境,与 OI 有一些差异,一些规范是在 OI 中不适用的。
这些代码规范中也有许多的可取之处,毕竟它们都是由许多大佬的经验总结而来的。可以选择一些觉得不错的部分对自己的码风进行调整、完善。作为 OIer,重要的还是自己看着顺眼。