This page looks best with JavaScript enabled

使用Clang-Format格式化代码

 ·  ☕ 8 min read · 👀... views

代码风格一致化和规范化,可以说是一直困扰我多年,并严重干扰我写代码效率的一个巨大难题。我总是会在写的时候思考变量、方法、类的命名,代码块的缩进,文件头的整齐导入等等。

然后,我发现了clang-format这个好东西,可以通过写一个统一的配置文件,在多个IDE上用这个统一的配置文件规范化代码风格。

基本使用

安装这些就不说了,无论是安装官方release还是自己编译LLVM都会有clang-format.exe,然后就可以用了

使用内置的风格格式化代码

1
2
3
4
5
6
// 以LLVM代码风格格式化main.cpp, 结果输出到stdout
clang-format -style=LLVM main.cpp
// 以LLVM代码风格格式化main.cpp, 结果直接写到main.cpp
clang-format -style=LLVM -i main.cpp
// 当然也支持对指定行格式化,格式化main.cpp的第1,2行
clang-format -lines=1:2 main.cpp

但是,经常,内置的style风格不符合我们的习惯,比如我就觉得LLVM的2空格缩进非常丑,所以希望在内置风格的基础上做一些修改,改成自己习惯的风格

1
2
3
// 导出风格配置文件
// 可选格式最好写预设那那几个写最接近你想要的格式. 比如我想要接近google C++ style的。 我就写-style=google
clang-format -style=可选格式名 -dump-config > .clang-format

将自定义的.clang-format配置文件放在项目目录下,然后就可以用 -style=file 将风格应用了

1
2
3
4
// 格式化的结果打到stdout(终端上)
clang-format -style=file main.c
// 直接修改到文件
clang-format -style=file -i main.c

vscode

装一个插件 Clang-Format 然后做一些配置

然后说一下几个最重要的配置:

  1. Clang-format: Executable : 这里需要填写clang-format.exe的路径。这个非常重要。clang-format实际上是一个还在研还在开发的工具,每个版本的迭代都会做很多修改。所以,如果用高版本导出的配置文件,往往会有很多字段是低版本的clang-format识别不了的,所以这个路径和版本一定要配对。
  2. Clang-format › Language › C: Style : 对C语言设置风格,可以是内置的风格,也可以是 file,即,用 .clang-format 文件中指定的风格进行格式化
  3. Clang-format: Fallback Style : 如果配置了对应语言的风格规则是 file 但是根目录下找不到 .clang-format 配置文件的话,会根据这个字段设置的内置风格对代码格式化

然后右键格式化就能格式化代码了

visual studio

按照网络上的说法是需要在VS里面安装一个叫ClangFormat的扩展程序才可以使用clang-format进行格式化代码。但是发现在VS2019里已经集成了这个功能了。

可以在 工具->选项->文本编辑器->C/C++->代码样式->格式设置 里面配置VS的clang-format使用

VS不像vsc一样可以直接右键格式化代码(我也想不明白为什么不做这个功能)。VS格式化代码在 编辑->高级 里:

  1. 设置文档的格式:Ctrl+K, Ctrl+D
  2. 设置选定内容的格式:Ctrl+K, Ctrl+F

Other trick

在使用clang-format的时候,有的时候我们确实要在一些迫不得已的场景下违反格式化的风格。比如,我们配置了clang-format的include排序,但是,在windows下,有的时候特定的include顺序是必要的。比如需要导入windows.h和wincrypt.h,就必须先导入windows.h再导入wincrypt.h,否则就会编译失败。但是如果配置了include升序排序规则,这个顺序明显是违反format规则的,所以需要做一下处理。可以使用这两行规定暂时的关闭clang-format

1
2
3
4
// clang-format off
#include <windows.h>
#include <wincrypt.h>
// clang-format on

一些配置含义

这里整理了一些从别的地方抄的配置含义,从配置上看应该是LLVM12的配置,已经与新版略有不同,用到时最好还是先去查官方文档

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# 语言: None, Cpp, Java, JavaScript, ObjC, Proto, TableGen, TextProto
Language:	Cpp
# 一般不全部重定义规则, 提供了BasedOnStyle标识让我们来重定义部分格式
BasedOnStyle:	LLVM
# 访问说明符(public、private等)的偏移
AccessModifierOffset:	-4
# 开括号(开圆括号、开尖括号、开方括号)后的对齐: Align, DontAlign, AlwaysBreak(总是在开括号后换行)
AlignAfterOpenBracket:	Align
# 连续赋值时,对齐所有等号
AlignConsecutiveAssignments:	true
# 连续声明时,对齐所有声明的变量名
AlignConsecutiveDeclarations:	true
# 左对齐逃脱换行(使用反斜杠换行)的反斜杠
AlignEscapedNewlinesLeft:	true
# 水平对齐二元和三元表达式的操作数
AlignOperands:	true
# 对齐连续的尾随的注释
AlignTrailingComments:	true
# 允许函数声明的所有参数在放在下一行
AllowAllParametersOfDeclarationOnNextLine:	true
# 允许短的块放在同一行
AllowShortBlocksOnASingleLine:	false
# 允许短的case标签放在同一行
AllowShortCaseLabelsOnASingleLine:	false
# 允许短的函数放在同一行: None, InlineOnly(定义在类中), Empty(空函数), Inline(定义在类中,空函数), All
AllowShortFunctionsOnASingleLine:	Empty
# 允许短的if语句保持在同一行
AllowShortIfStatementsOnASingleLine:	false
# 允许短的循环保持在同一行
AllowShortLoopsOnASingleLine:	false
# 总是在定义返回类型后换行(deprecated)
AlwaysBreakAfterDefinitionReturnType:	None
# 总是在返回类型后换行: None, All, TopLevel(顶级函数,不包括在类中的函数), 
#   AllDefinitions(所有的定义,不包括声明), TopLevelDefinitions(所有的顶级函数的定义)
AlwaysBreakAfterReturnType:	None
# 总是在多行string字面量前换行
AlwaysBreakBeforeMultilineStrings:	false
# 总是在template声明后换行
AlwaysBreakTemplateDeclarations:	false
# false表示函数实参要么都在同一行,要么都各自一行
BinPackArguments:	true
# false表示所有形参要么都在同一行,要么都各自一行
BinPackParameters:	true
# 大括号换行,只有当BreakBeforeBraces设置为Custom时才有效
BraceWrapping:   
  # class定义后面
  AfterClass:	false
  # 控制语句后面
  AfterControlStatement:	false
  # enum定义后面
  AfterEnum:	false
  # 函数定义后面
  AfterFunction:	false
  # 命名空间定义后面
  AfterNamespace:	false
  # ObjC定义后面
  AfterObjCDeclaration:	false
  # struct定义后面
  AfterStruct:	false
  # union定义后面
  AfterUnion:	false
  # catch之前
  BeforeCatch:	true
  # else之前
  BeforeElse:	true
  # 缩进大括号
  IndentBraces:	false
# 在二元运算符前换行: None(在操作符后换行), NonAssignment(在非赋值的操作符前换行), All(在操作符前换行)
BreakBeforeBinaryOperators:	NonAssignment
# 在大括号前换行: Attach(始终将大括号附加到周围的上下文), Linux(除函数、命名空间和类定义,与Attach类似), 
#   Mozilla(除枚举、函数、记录定义,与Attach类似), Stroustrup(除函数定义、catch、else,与Attach类似), 
#   Allman(总是在大括号前换行), GNU(总是在大括号前换行,并对于控制语句的大括号增加额外的缩进), WebKit(在函数前换行), Custom
#   注:这里认为语句块也属于函数
BreakBeforeBraces:	Custom
# 在三元运算符前换行
BreakBeforeTernaryOperators:	true
# 在构造函数的初始化列表的逗号前换行
BreakConstructorInitializersBeforeComma:	false
# 每行字符的限制,0表示没有限制
ColumnLimit:	200
# 描述具有特殊意义的注释的正则表达式,它不应该被分割为多行或以其它方式改变
CommentPragmas:	'^ IWYU pragma:'
# 构造函数的初始化列表要么都在同一行,要么都各自一行
ConstructorInitializerAllOnOneLineOrOnePerLine:	false
# 构造函数的初始化列表的缩进宽度
ConstructorInitializerIndentWidth:	4
# 延续的行的缩进宽度
ContinuationIndentWidth:	4
# 去除C++11的列表初始化的大括号{后和}前的空格
Cpp11BracedListStyle:	false
# 继承最常用的指针和引用的对齐方式
DerivePointerAlignment:	false
# 关闭格式化
DisableFormat:	false
# 自动检测函数的调用和定义是否被格式为每行一个参数(Experimental)
ExperimentalAutoDetectBinPacking:	false
# 需要被解读为foreach循环而不是函数调用的宏
ForEachMacros:	[ foreach, Q_FOREACH, BOOST_FOREACH ]
# 对#include进行排序,匹配了某正则表达式的#include拥有对应的优先级,匹配不到的则默认优先级为INT_MAX(优先级越小排序越靠前),
#   可以定义负数优先级从而保证某些#include永远在最前面
IncludeCategories: 
  - Regex:	'^"(llvm|llvm-c|clang|clang-c)/'
    Priority:	2
  - Regex:	'^(<|"(gtest|isl|json)/)'
    Priority:	3
  - Regex:	'.*'
    Priority:	1
# 缩进case标签
IndentCaseLabels:	false
# 缩进宽度
IndentWidth:	4
# 函数返回类型换行时,缩进函数声明或函数定义的函数名
IndentWrappedFunctionNames:	false
# 保留在块开始处的空行
KeepEmptyLinesAtTheStartOfBlocks:	true
# 开始一个块的宏的正则表达式
MacroBlockBegin:	''
# 结束一个块的宏的正则表达式
MacroBlockEnd:	''
# 连续空行的最大数量
MaxEmptyLinesToKeep:	1
# 命名空间的缩进: None, Inner(缩进嵌套的命名空间中的内容), All
NamespaceIndentation:	Inner
# 使用ObjC块时缩进宽度
ObjCBlockIndentWidth:	4
# 在ObjC的@property后添加一个空格
ObjCSpaceAfterProperty:	false
# 在ObjC的protocol列表前添加一个空格
ObjCSpaceBeforeProtocolList:	true
# 在call(后对函数调用换行的penalty
PenaltyBreakBeforeFirstCallParameter:	19
# 在一个注释中引入换行的penalty
PenaltyBreakComment:	300
# 第一次在<<前换行的penalty
PenaltyBreakFirstLessLess:	120
# 在一个字符串字面量中引入换行的penalty
PenaltyBreakString:	1000
# 对于每个在行字符数限制之外的字符的penalty
PenaltyExcessCharacter:	1000000
# 将函数的返回类型放到它自己的行的penalty
PenaltyReturnTypeOnItsOwnLine:	60
# 指针和引用的对齐: Left, Right, Middle
PointerAlignment:	Left
# 允许重新排版注释
ReflowComments:	true
# 允许排序#include
SortIncludes:	true
# 在C风格类型转换后添加空格
SpaceAfterCStyleCast:	false
# 在赋值运算符之前添加空格
SpaceBeforeAssignmentOperators:	true
# 开圆括号之前添加一个空格: Never, ControlStatements, Always
SpaceBeforeParens:	ControlStatements
# 在空的圆括号中添加空格
SpaceInEmptyParentheses:	false
# 在尾随的评论前添加的空格数(只适用于//)
SpacesBeforeTrailingComments:	2
# 在尖括号的<后和>前添加空格
SpacesInAngles:	true
# 在容器(ObjC和JavaScript的数组和字典等)字面量中添加空格
SpacesInContainerLiterals:	true
# 在C风格类型转换的括号中添加空格
SpacesInCStyleCastParentheses:	true
# 在圆括号的(后和)前添加空格
SpacesInParentheses:	true
# 在方括号的[后和]前添加空格,lamda表达式和未指明大小的数组的声明不受影响
SpacesInSquareBrackets:	true
# 标准: Cpp03, Cpp11, Auto
Standard:	Cpp11
# tab宽度
TabWidth:	4
# 使用tab字符: Never, ForIndentation, ForContinuationAndIndentation, Always
UseTab:	Never

参考资料

  1. https://www.cnblogs.com/__tudou__/p/13322854.html
  2. https://blog.csdn.net/sinat_26685721/article/details/109906411
Share on

Qfrost
WRITTEN BY
Qfrost
CTFer, Anti-Cheater, LLVM Committer