google c++ 代码规范关键条目总结

GOOGLE C++代码规范总结:

开始一个项目之前,首先应该明确编码规范。一个好的编码习惯可以让代码更易于阅读,同时也能让程序员避免犯一些常见的错误。

为了便于快速参考,此帖仅仅提取了关键点,具体的详解请参考官方地址:
英文:Google C++ Style Guide
中文:C++ 风格指南

Content

一、头文件
二、作用域
三、类
四、函数
五、来自GOOGLE的技巧
六、其他C++特性
七、命名约定
八、注释
九、格式
十、例外


一、头文件

  1. 通常每一个 .cc 文件都有一个对应的 .h 文件。
  2. 头文件应该能够自给自足(self-contained)。
  3. 所有头文件都应该使用 #define 来防止头文件被多重包含, 命名格式是<PROJECT>_<PATH>_<FILE>_H_
  4. 尽可能地避免使用前置声明。
  5. 只有当函数只有不大于 10 行时才将其定义为内联函数。
  6. 头文件包含顺序:
    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_

二、作用域

  1. 鼓励在 .cc 文件中使用匿名命名空间或 static 声明。
  2. 禁止使用 using 指示(using-directive)和内联命名空间(inline namespace)。
  3. 尽量不要用裸的全局函数,不要用类的静态方法模拟出命名空间的效果。
  4. 将函数变量尽可能置于最小作用域内(考虑效率的循环体是个例外), 并在变量声明时进行初始化。
  5. 禁止定义静态储存周期非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

三、类

  1. 不要在构造函数中调用虚函数,也不要在无法报出错误时进行可能失败的初始化。
  2. 对于转换运算符和单参数构造函数, 使用explicit关键字。
  3. 如果你的类型需要,就让它们支持拷贝 / 移动。 否则,就把隐式产生的拷贝和移动函数禁用。
  4. 仅当只有数据成员时使用struct, 其它一概使用class
  5. 优先考虑组合。如果用继承的话,定义为public继承。
  6. 只在以下情况我们才允许多重继承: 最多只有一个基类是非抽象类;其它基类都是以 Interface 为后缀的 纯接口类。
  7. 除少数特定环境外,不要重载运算符。也不要创建用户定义字面量。
  8. 所有数据成员声明为private, 除非是static const类型成员。
  9. 类定义一般应以public:开始, 后跟protected:,最后是private:。每部分的内容按如下顺序:
    1. 类型 (包括 typedef, using 和嵌套的结构体与类)
    2. 常量
    3. 工厂函数, 构造函数, 赋值运算符, 析构函数, 其它函数(从左向右)
    4. 数据成员
  1. 只有那些普通的, 或性能关键且短小的函数可以内联在类定义中。

四、函数

  1. 通常函数的参数顺序为: 输入参数在先, 后跟输出参数。
  2. 倾向于编写简短, 凝练的函数(不超过40行)。
  3. 所有引用参数都必须是const。输入参数是值参或const引用,输出参数为指针。输入参数可以是const指针, 但决不能是非const的引用参数, 除非特殊要求(如swap)。
  4. 函数重载要让人一目了然。比如用 AppendString() 和 AppendInt() 等代替重载多个 Append()。
  5. 只允许在非虚函数中使用缺省参数,且必须保证缺省参数的值始终一致。一般情况下建议使用函数重载。如果在每个调用点缺省参数的值都有可能不同, 缺省函数也不允许使用。
  6. 只有在常规写法 (返回类型前置) 不便于书写或不便于阅读时使用返回类型后置语法。

五、来自GOOGLE的技巧

  1. 动态分配出的对象最好有单一且固定的所有主, 并通过智能指针传递所有权。
  2. 使用cpplint.py检查风格错误。

六、其他C++特性

  1. 只在定义移动构造函数与移动赋值操作时使用右值引用. 不要使用 std::forward。
  2. 不允许使用变长数组和 alloca()。
  3. 通常友元应该定义在同一文件内, 避免代码读者跑到其它文件查找使用该私有成员的类。
  4. 不使用 C++ 异常。
  5. 禁止使用 RTTI。
  6. 不要使用 C 风格类型转换,而应该使用 C++ 风格。
  7. 只在记录日志时使用流。
  8. 对于迭代器和其他模板对象使用前缀形式 (++i) 的自增,自减运算符。
  9. 在任何可能的情况下都要使用 const。对于int const *foo 和 const int* foo,提倡但不强制 const 在前,但要保持代码的一致性。
  10. 在 C++11 里,用 constexpr 来定义真正的常量,或实现常量初始化。
  11. C++ 内建整型中, 仅使用 int。如果需要,使用<stdint.h> 中长度精确的整型。
  12. 使用断言来指出变量为非负数, 而不是使用无符号型。
  13. 代码应该对 64 位和 32 位系统友好。参考
  14. 创建 64 位常量时使用 LL 或 ULL 作为后缀。
  15. 使用宏时要非常谨慎, 尽量以内联函数, 枚举和常量代替之。
  16. 整数用 0, 实数用 0.0, 指针用 nullptr 或 NULL, 字符 (串) 用 ‘\0’。
  17. 尽可能用 sizeof(varname) 代替 sizeof(type)。
  18. 用 auto 绕过烦琐的类型名,只要可读性好就继续用,别用在局部变量之外的地方。
  19. 可以用列表初始化。
  20. 适当使用 lambda 表达式,所有捕获都要显式写出来。
  21. 不要使用复杂的模板编程。
  22. 只使用 Boost 中被认可的库。参考
  23. 适当用 C++11。

七、命名约定

  1. 命名要有描述性,少用缩写(广为人知的缩写是允许的)。
  2. 代码文件名要全部小写,可以包含下划线 “_” 或连字符 “-”,依照项目的约定. 如果没有约定, 那么 “_” 更好。不要使用已经存在于/usr/include下的文件名。
  3. C++ 文件要以 .cc 结尾,头文件以 .h 结尾。专门插入文本的文件则以 .inc 结尾。
  4. 类型名称的每个单词首字母均大写,不包含下划线:MyExcitingClass,MyExcitingEnum。
  5. 变量 (包括函数参数) 和数据成员名一律小写,单词之间用下划线连接。类的成员变量以下划线结尾,但结构体的就不用。
  6. 声明为 constexpr 或 const 的变量,或在程序运行期间其值始终保持不变的, 命名时以 “k” 开头, 大小写混合,如kDaysInAWeek
  7. 常规函数使用大小写混合,取值和设值函数则要求与变量名匹配:MyExcitingFunction(),MyExcitingMethod(),my_exciting_member_variable(),set_my_exciting_member_variable()。
  8. 命名空间以小写字母命名. 最高级命名空间的名字取决于项目名称。
  9. 枚举的命名应当和 常量(优先) 或 宏 一致: kEnumName 或是 ENUM_NAME。
  10. 通常不应该使用宏。若要用,用大写加下划线。如MY_MACRO。
  11. 如果你命名的实体与已有 C/C++ 实体相似, 可参考现有命名策略。

八、注释

  1. 使用 // 或 /* */,统一就好。
  2. 在每一个文件开头加入 版权公告 和 文件内容 信息。
  3. 每个类的定义都要附带一份注释, 描述类的功能和用法。
// 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 {
  ...
};
  1. 类的声明和定义分开了(例如分别放在了 .h 和 .cc 文件中),此时,描述类用法的注释应当和接口定义放在一起,描述类的操作和实现的注释应当和实现放在一起。不要在 .h 和 .cc 之间复制注释。
  2. 函数声明处的注释描述函数功能,定义处的注释描述函数实现。注释使用叙述式 (“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;
  1. 函数定义处的注释重点要放在如何实现上。
  2. 如果变量的变量名和类型名不足以说明,就应该用注释说明其用途。
  3. 在巧妙或者晦涩的代码段前要加代码前注释:
// 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;
}
  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. */
  1. 对于函数参数,用具名变量代替大段而复杂的嵌套表达式。
  2. 不要描述显而易见的现象,不要用自然语言翻译代码作为注释。
  3. 通常是完整的带结束标点的叙述语句,语法及标点风格要统一。
  4. 对那些临时的, 短期的解决方案, 或已经够好但仍不完美的代码使用 TODO 注释。
// TODO(kl@gmail.com): Use a "*" here for concatenation operator.
// TODO(Zeke) change this to use relations.
// TODO(bug 12345): remove the "Last visitors" feature
  1. 通过弃用注释(DEPRECATED)以标记某接口点已弃用。

九、格式

  1. 每一行代码字符数尽量不超过80。
  2. 尽量不使用非 ASCII 字符,使用时必须使用 UTF-8 编码。
  3. 不应将用户界面的文本硬编码到源代码中。
  4. 缩进只使用空格,每次2个空格。不要在代码中使用制表符。
  5. 返回类型和函数名在同一行,参数也尽量放在同一行,如果放不下就对形参分行,分行方式与函数调用一致,其余细节如下:
// 右圆括号和函数名、第一个参数名间没有空格。
// 返回类型如果与函数名分行的话不缩进。
ReturnType LongClassName::ReallyReallyReallyLongFunctionName(
    Type par_name1,  // 4 space indent
    Type par_name2,
    Type par_name3) { // 右圆括号和左大括号间有一个空格且在同一行
  DoSomething();  // 2 space indent
  ...
} // 单独一行

// 未被使用的参数如果其用途不明显的话, 在函数定义处将参数名注释起来
void Circle::Rotate(double /*radians*/) {} 

// 属性,和展开为属性的宏,写在函数声明或定义的最前面
MUST_USE_RESULT bool IsOK();
  1. 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());
  1. 函数调用格式:
// 在同一行
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);
  1. 列表初始化格式:
// 一行列表初始化示范.
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}};
  1. 条件语句格式,重要的是坚持一种并始终保持下去:
if (condition) {  // if后有空格,圆括号里没有空格.
  ...  // 2 空格缩进.
} else if (...) {  // else 与 if 的右括号同一行.
  ...
} else {
  ...
}

if (x == kFoo) return new Foo(); // 简短的情况,可以不用大括号

if (condition)
  DoSomething();  // 2 空格缩进.

// 只要其中一个分支用了大括号, 两个分支都要用上大括号.
if (condition) {
  foo;
} else {
  bar;
}
  1. 循环语句和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 的部分之一.
  1. ".“或”->"前后不要有空格. 指针/取地址操作符 (*, &) 之后不能有空格。
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;  // 差 - & 两边都有空格.
  1. 布尔表达式格式:
if (this_one_thing > this_other_thing &&
    a_third_thing == a_fourth_thing &&
    yet_another && last_one) {
  ...
}
  1. return语句格式:
return result;                  // 返回值很简单, 没有圆括号.

// 可以用圆括号把复杂表达式圈起来, 改善可读性.
return (some_long_condition &&
        another_condition);

return (value);                // 差 - 毕竟您从来不会写 var = (value);
return(result);                // 差 - return 可不是函数!
  1. 变量初始化:
// 以下都可以
int x = 3;
int x(3);
int x{3};
string name("Some Name");
string name = "Some Name";
string name{"Some Name"};
  1. 预处理指令从行首开始:
  if (lopsided_score) {
#if DISASTER_PENDING      // 正确 - 从行首开始
    DropEverything();
# if NOTIFY               // 非必要 - # 后跟空格
    NotifyClient();
# endif
#endif
    BackToNormal();
  }
  1. 访问控制块的声明依次序是 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_;
};
  1. 构造函数初始化列表:
// 如果所有变量能放在同一行:
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 indent
      some_other_var_(var + 1) {  // lined up
  DoSomething();
}

// 右大括号 } 可以和左大括号 { 放在同一行
// 如果这样做合适的话
MyClass::MyClass(int var)
    : some_var_(var) {}
  1. 命名空间内容不缩进。声明嵌套命名空间时,每个命名空间都独立成行。
  2. 行尾不要加多余的空格。
  3. 操作符:
// 赋值运算符前后总是有空格.
x = 0;

// 其它二元操作符也前后恒有空格, 不过对于表达式的子式可以不加空格.
// 圆括号内部没有紧邻空格.
v = w * x + y / z;
v = w*x + y/z;
v = w * (x + z);

// 在参数和一元操作符之间不加空格.
x = -5;
++x;
if (x && !y)
  ...
  1. 不在万不得已,不要使用空行。两函数之间的空行不要超过两行。

十、例外

  1. 已有既定风格的项目。
  2. Windows代码。
2020/05/07 22:57
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值