条目总结"/>
google c++ 代码规范关键条目总结
GOOGLE C++代码规范总结:
开始一个项目之前,首先应该明确编码规范。一个好的编码习惯可以让代码更易于阅读,同时也能让程序员避免犯一些常见的错误。
为了便于快速参考,此帖仅仅提取了关键点,具体的详解请参考官方地址:
英文:Google C++ Style Guide
中文:C++ 风格指南
Content
一、头文件
二、作用域
三、类
四、函数
五、来自GOOGLE的技巧
六、其他C++特性
七、命名约定
八、注释
九、格式
十、例外
一、头文件
- 通常每一个 文件都有一个对应的 .h 文件。
- 头文件应该能够自给自足(self-contained)。
- 所有头文件都应该使用 #define 来防止头文件被多重包含, 命名格式是
<PROJECT>_<PATH>_<FILE>_H_
。 - 尽可能地避免使用前置声明。
- 只有当函数只有不大于 10 行时才将其定义为内联函数。
- 头文件包含顺序:
1. 相关头文件 2. C 系统文件 3. C++ 系统文件 4. 其他库的 .h 文件 5. 本项目内 .h 文件
例子:
#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_#include "foo/public/fooserver.h" // 优先位置#include <sys/types.h>
#include <unistd.h>#include <hash_map>
#include <vector>#include "base/basictypes.h"
#include "base/commandlineflags.h"
#include "foo/public/bar.h"#endif // FOO_BAR_BAZ_H_
二、作用域
- 鼓励在 文件中使用匿名命名空间或 static 声明。
- 禁止使用 using 指示(using-directive)和内联命名空间(inline namespace)。
- 尽量不要用裸的全局函数,不要用类的静态方法模拟出命名空间的效果。
- 将函数变量尽可能置于最小作用域内(考虑效率的循环体是个例外), 并在变量声明时进行初始化。
- 禁止定义静态储存周期非POD变量。
例子:
#include "a.h"DEFINE_FLAG(bool, someflag, false, "dummy flag");namespace a {inline void my_inline_function() { // 左对齐,不要缩进// 限制在一个函数中的命名空间别名namespace baz = ::foo::bar::baz;...
} } // namespace a
三、类
- 不要在构造函数中调用虚函数,也不要在无法报出错误时进行可能失败的初始化。
- 对于转换运算符和单参数构造函数, 使用
explicit
关键字。 - 如果你的类型需要,就让它们支持拷贝 / 移动。 否则,就把隐式产生的拷贝和移动函数禁用。
- 仅当只有数据成员时使用
struct
, 其它一概使用class
。 - 优先考虑组合。如果用继承的话,定义为
public
继承。 - 只在以下情况我们才允许多重继承: 最多只有一个基类是非抽象类;其它基类都是以 Interface 为后缀的 纯接口类。
- 除少数特定环境外,不要重载运算符。也不要创建用户定义字面量。
- 所有数据成员声明为
private
, 除非是static const
类型成员。 - 类定义一般应以
public:
开始, 后跟protected:
,最后是private:
。每部分的内容按如下顺序:
1. 类型 (包括 typedef, using 和嵌套的结构体与类)2. 常量3. 工厂函数, 构造函数, 赋值运算符, 析构函数, 其它函数(从左向右)4. 数据成员
- 只有那些普通的, 或性能关键且短小的函数可以内联在类定义中。
四、函数
- 通常函数的参数顺序为: 输入参数在先, 后跟输出参数。
- 倾向于编写简短, 凝练的函数(不超过40行)。
- 所有引用参数都必须是const。输入参数是值参或
const
引用,输出参数为指针。输入参数可以是const
指针, 但决不能是非const
的引用参数, 除非特殊要求(如swap)。 - 函数重载要让人一目了然。比如用 AppendString() 和 AppendInt() 等代替重载多个 Append()。
- 只允许在非虚函数中使用缺省参数,且必须保证缺省参数的值始终一致。一般情况下建议使用函数重载。如果在每个调用点缺省参数的值都有可能不同, 缺省函数也不允许使用。
- 只有在常规写法 (返回类型前置) 不便于书写或不便于阅读时使用返回类型后置语法。
五、来自GOOGLE的技巧
- 动态分配出的对象最好有单一且固定的所有主, 并通过智能指针传递所有权。
- 使用cpplint.py检查风格错误。
六、其他C++特性
- 只在定义移动构造函数与移动赋值操作时使用右值引用. 不要使用 std::forward。
- 不允许使用变长数组和 alloca()。
- 通常友元应该定义在同一文件内, 避免代码读者跑到其它文件查找使用该私有成员的类。
- 不使用 C++ 异常。
- 禁止使用 RTTI。
- 不要使用 C 风格类型转换,而应该使用 C++ 风格。
- 只在记录日志时使用流。
- 对于迭代器和其他模板对象使用前缀形式 (++i) 的自增,自减运算符。
- 在任何可能的情况下都要使用 const。对于int const *foo 和 const int* foo,提倡但不强制 const 在前,但要保持代码的一致性。
- 在 C++11 里,用 constexpr 来定义真正的常量,或实现常量初始化。
- C++ 内建整型中, 仅使用 int。如果需要,使用<stdint.h> 中长度精确的整型。
- 使用断言来指出变量为非负数, 而不是使用无符号型。
- 代码应该对 64 位和 32 位系统友好。参考
- 创建 64 位常量时使用 LL 或 ULL 作为后缀。
- 使用宏时要非常谨慎, 尽量以内联函数, 枚举和常量代替之。
- 整数用 0, 实数用 0.0, 指针用 nullptr 或 NULL, 字符 (串) 用 ‘\0’。
- 尽可能用 sizeof(varname) 代替 sizeof(type)。
- 用 auto 绕过烦琐的类型名,只要可读性好就继续用,别用在局部变量之外的地方。
- 可以用列表初始化。
- 适当使用 lambda 表达式,所有捕获都要显式写出来。
- 不要使用复杂的模板编程。
- 只使用 Boost 中被认可的库。参考
- 适当用 C++11。
七、命名约定
- 命名要有描述性,少用缩写(广为人知的缩写是允许的)。
- 代码文件名要全部小写,可以包含下划线 “_” 或连字符 “-”,依照项目的约定. 如果没有约定, 那么 “_” 更好。不要使用已经存在于
/usr/include
下的文件名。 - C++ 文件要以 结尾,头文件以 .h 结尾。专门插入文本的文件则以 .inc 结尾。
- 类型名称的每个单词首字母均大写,不包含下划线:MyExcitingClass,MyExcitingEnum。
- 变量 (包括函数参数) 和数据成员名一律小写,单词之间用下划线连接。类的成员变量以下划线结尾,但结构体的就不用。
- 声明为 constexpr 或 const 的变量,或在程序运行期间其值始终保持不变的, 命名时以 “k” 开头, 大小写混合,如
kDaysInAWeek
。 - 常规函数使用大小写混合,取值和设值函数则要求与变量名匹配:MyExcitingFunction(),MyExcitingMethod(),my_exciting_member_variable(),set_my_exciting_member_variable()。
- 命名空间以小写字母命名. 最高级命名空间的名字取决于项目名称。
- 枚举的命名应当和 常量(优先) 或 宏 一致: kEnumName 或是 ENUM_NAME。
- 通常不应该使用宏。若要用,用大写加下划线。如MY_MACRO。
- 如果你命名的实体与已有 C/C++ 实体相似, 可参考现有命名策略。
八、注释
- 使用 // 或 /* */,统一就好。
- 在每一个文件开头加入 版权公告 和 文件内容 信息。
- 每个类的定义都要附带一份注释, 描述类的功能和用法。
// Iterates over the contents of a GargantuanTable.
// Example:
// GargantuanTableIterator* iter = table->NewIterator();
// for (iter->Seek("foo"); !iter->done(); iter->Next()) {
// process(iter->key(), iter->value());
// }
// delete iter;
class GargantuanTableIterator {...
};
- 类的声明和定义分开了(例如分别放在了 .h 和 文件中),此时,描述类用法的注释应当和接口定义放在一起,描述类的操作和实现的注释应当和实现放在一起。不要在 .h 和 之间复制注释。
- 函数声明处的注释描述函数功能,定义处的注释描述函数实现。注释使用叙述式 (“Opens the file”) 而非指令式(“Open the file”)。要叙述的内容如下(也要避免啰嗦,显而易见的就不必指明了):
函数的输入输出.对类成员函数而言: 函数调用期间对象是否需要保持引用参数, 是否会释放这些参数.函数是否分配了必须由调用者释放的空间.参数是否可以为空指针.是否存在函数使用上的性能隐患.如果函数是可重入的, 其同步前提是什么?
// Returns an iterator for this table. It is the client's
// responsibility to delete the iterator when it is done with it,
// and it must not use the iterator once the GargantuanTable object
// on which the iterator was created has been deleted.
//
// The iterator is initially positioned at the beginning of the table.
//
// This method is equivalent to:
// Iterator* iter = table->NewIterator();
// iter->Seek("");
// return iter;
// If you are going to immediately seek to another place in the
// returned iterator, it will be faster to use NewIterator()
// and avoid the extra seek.
Iterator* GetIterator() const;
- 函数定义处的注释重点要放在如何实现上。
- 如果变量的变量名和类型名不足以说明,就应该用注释说明其用途。
- 在巧妙或者晦涩的代码段前要加代码前注释:
// Divide result by two, taking into account that x
// contains the carry from the add.
for (int i = 0; i < result->size(); i++) {x = (x << 8) + (*result)[i];(*result)[i] = x >> 1;x &= 1;
}
- 在巧妙或者晦涩的代码行后要加行注释,在行尾空两格进行注释或多行时注意对齐:
DoSomething(); // Comment here so the comments line up.
DoSomethingElseThatIsLonger(); // Two spaces between the code and the comment.
{ // One space before comment when opening a new scope is allowed,// thus the comment lines up with the following comments and code.DoSomethingElse(); // Two spaces before line comments normally.
}
std::vector<string> list{// Comments in braced lists describe the next element..."First item",// .. and should be aligned appropriately.
"Second item"};
DoSomething(); /* For trailing block comments, one space is fine. */
- 对于函数参数,用具名变量代替大段而复杂的嵌套表达式。
- 不要描述显而易见的现象,不要用自然语言翻译代码作为注释。
- 通常是完整的带结束标点的叙述语句,语法及标点风格要统一。
- 对那些临时的, 短期的解决方案, 或已经够好但仍不完美的代码使用 TODO 注释。
// TODO(kl@gmail): Use a "*" here for concatenation operator.
// TODO(Zeke) change this to use relations.
// TODO(bug 12345): remove the "Last visitors" feature
- 通过弃用注释(DEPRECATED)以标记某接口点已弃用。
九、格式
- 每一行代码字符数尽量不超过80。
- 尽量不使用非 ASCII 字符,使用时必须使用 UTF-8 编码。
- 不应将用户界面的文本硬编码到源代码中。
- 缩进只使用空格,每次2个空格。不要在代码中使用制表符。
- 返回类型和函数名在同一行,参数也尽量放在同一行,如果放不下就对形参分行,分行方式与函数调用一致,其余细节如下:
// 右圆括号和函数名、第一个参数名间没有空格。
// 返回类型如果与函数名分行的话不缩进。
ReturnType LongClassName::ReallyReallyReallyLongFunctionName(Type par_name1, // 4 space indentType par_name2,Type par_name3) { // 右圆括号和左大括号间有一个空格且在同一行DoSomething(); // 2 space indent...
} // 单独一行// 未被使用的参数如果其用途不明显的话, 在函数定义处将参数名注释起来
void Circle::Rotate(double /*radians*/) {} // 属性,和展开为属性的宏,写在函数声明或定义的最前面
MUST_USE_RESULT bool IsOK();
- lambda表达式格式和函数一样:
int x = 0;
auto add_to_x = [&x](int n) { x += n; };std::set<int> blacklist = {7, 8, 9};
std::vector<int> digits = {3, 9, 1, 8, 4, 7, 1};
digits.erase(std::remove_if(digits.begin(), digits.end(), [&blacklist](int i) {return blacklist.find(i) != blacklist.end();}),digits.end());
- 函数调用格式:
// 在同一行
bool retval = DoSomething(argument1, argument2, argument3);// 对参数分行
bool retval = DoSomething(averyveryveryverylongargument1,argument2, argument3);if (...) {......if (...) {// 缩进 4 空格对参数分行DoSomething(argument1, argument2, // 4 空格缩进argument3, argument4);}// 复杂表达式参数,以具名对象方式传入
int my_heuristic = scores[x] * y + bases[x];
bool retval = DoSomething(my_heuristic, x, y, z);// 也可以直接用,加上注释
bool retval = DoSomething(scores[x] * y + bases[x], // Score heuristic.x, y, z);// 如果参数有格式,也可以使用参数本身的格式
my_widget.Transform(x1, x2, x3,y1, y2, y3,z1, z2, z3);
- 列表初始化格式:
// 一行列表初始化示范.
return {foo, bar};
functioncall({foo, bar});
pair<int, int> p{foo, bar};// 当不得不断行时.
SomeFunction({"assume a zero-length name before {"}, // 假设在 { 前有长度为零的名字.some_other_function_parameter);
SomeType variable{some, other, values,{"assume a zero-length name before {"}, // 假设在 { 前有长度为零的名字.SomeOtherType{"Very long string requiring the surrounding breaks.", // 非常长的字符串, 前后都需要断行.some, other values},SomeOtherType{"Slightly shorter string", // 稍短的字符串.some, other, values}};
SomeType variable{"This is too long to fit all in one line"}; // 字符串过长, 因此无法放在同一行.
MyType m = { // 注意了, 您可以在 { 前断行.superlongvariablename1,superlongvariablename2,{short, interior, list},{interiorwrappinglist,interiorwrappinglist2}};
- 条件语句格式,重要的是坚持一种并始终保持下去:
if (condition) { // if后有空格,圆括号里没有空格.... // 2 空格缩进.
} else if (...) { // else 与 if 的右括号同一行....
} else {...
}if (x == kFoo) return new Foo(); // 简短的情况,可以不用大括号if (condition)DoSomething(); // 2 空格缩进.// 只要其中一个分支用了大括号, 两个分支都要用上大括号.
if (condition) {foo;
} else {bar;
}
- 循环语句和switch语句:
switch (var) {case 0: { // 2 空格缩进... // 4 空格缩进break;}case 1: { //大括号可以用可以不用...break;}default: {assert(false); // 如果永远执行不到这里,就加一条assert}
}// 循环体单行语句可以不用大括号
for (int i = 0; i < kSomeNumber; ++i)printf("I love you\n");// 也可以用大括号
for (int i = 0; i < kSomeNumber; ++i) {printf("I take it back\n");
}while (condition) {// 反复循环直到条件失效.
}
for (int i = 0; i < kSomeNumber; ++i) {} // 好 - 空循环体.
while (condition) continue; // 好 - contunue 表明没有逻辑.
while (condition); // 差 - 看起来仅仅只是 while/loop 的部分之一.
- ".“或”->"前后不要有空格. 指针/取地址操作符 (*, &) 之后不能有空格。
x = *p;
p = &x;
x = r.y;
x = r->y;// 好, 空格前置.
char *c;
const string &str;// 好, 空格后置.
char* c;
const string& str;
int x, *y; // 不允许 - 在多重声明中不能使用 & 或 *
char * c; // 差 - * 两边都有空格
const string & str; // 差 - & 两边都有空格.
- 布尔表达式格式:
if (this_one_thing > this_other_thing &&a_third_thing == a_fourth_thing &&yet_another && last_one) {...
}
- return语句格式:
return result; // 返回值很简单, 没有圆括号.// 可以用圆括号把复杂表达式圈起来, 改善可读性.
return (some_long_condition &&another_condition);return (value); // 差 - 毕竟您从来不会写 var = (value);
return(result); // 差 - return 可不是函数!
- 变量初始化:
// 以下都可以
int x = 3;
int x(3);
int x{3};
string name("Some Name");
string name = "Some Name";
string name{"Some Name"};
- 预处理指令从行首开始:
if (lopsided_score) {
#if DISASTER_PENDING // 正确 - 从行首开始DropEverything();
# if NOTIFY // 非必要 - # 后跟空格NotifyClient();
# endif
#endifBackToNormal();}
- 访问控制块的声明依次序是 public:, protected:, private:, 每个都缩进 1 个空格:
class MyClass : public OtherClass {public: // 注意有一个空格的缩进MyClass(); // 标准的两空格缩进explicit MyClass(int var);~MyClass() {}void SomeFunction();void SomeFunctionThatDoesNothing() {}void set_some_var(int var) { some_var_ = var; }int some_var() const { return some_var_; }private:bool SomeInternalFunction();int some_var_;int some_other_var_;
};
- 构造函数初始化列表:
// 如果所有变量能放在同一行:
MyClass::MyClass(int var) : some_var_(var) {DoSomething();
}// 如果不能放在同一行,
// 必须置于冒号后, 并缩进 4 个空格
MyClass::MyClass(int var): some_var_(var), some_other_var_(var + 1) {DoSomething();
}// 如果初始化列表需要置于多行, 将每一个成员放在单独的一行
// 并逐行对齐
MyClass::MyClass(int var): some_var_(var), // 4 space indentsome_other_var_(var + 1) { // lined upDoSomething();
}// 右大括号 } 可以和左大括号 { 放在同一行
// 如果这样做合适的话
MyClass::MyClass(int var): some_var_(var) {}
- 命名空间内容不缩进。声明嵌套命名空间时,每个命名空间都独立成行。
- 行尾不要加多余的空格。
- 操作符:
// 赋值运算符前后总是有空格.
x = 0;// 其它二元操作符也前后恒有空格, 不过对于表达式的子式可以不加空格.
// 圆括号内部没有紧邻空格.
v = w * x + y / z;
v = w*x + y/z;
v = w * (x + z);// 在参数和一元操作符之间不加空格.
x = -5;
++x;
if (x && !y)...
- 不在万不得已,不要使用空行。两函数之间的空行不要超过两行。
十、例外
- 已有既定风格的项目。
- Windows代码。
更多推荐
google c++ 代码规范关键条目总结
发布评论