Google C++代码规范 一万字笔记超详细

本文详细介绍了C++编程中关于命名约定、文件和变量命名、类型定义、常量与宏、函数和循环结构的格式规范,强调代码的可读性和一致性。此外,还涵盖了头文件保护、预处理指令、内联函数、构造函数、注释风格以及作用域使用等方面的最佳实践,旨在提升代码质量和维护性。
摘要由CSDN通过智能技术生成

命名约定

通用命名规则
  • 函数命名, 变量命名, 文件命名要有描述性; 少用缩写

    • 尽可能使用描述性的命名, 别心疼空间, 毕竟相比之下让代码易于新读者理解更重要. 不要用只有项目开发者能理解的缩写, 也不要通过砍掉几个字母来缩写单词
      int price_count_reader;    // 无缩写
      int num_errors;            // "num" 是一个常见的写法
      int num_dns_connections;   // 人人都知道 "DNS" 是什么
      
      int n;                     // 毫无意义.
      int nerr;                  // 含糊不清的缩写.
      int n_comp_conns;          // 含糊不清的缩写.
      int wgc_connections;       // 只有贵团队知道是什么意思.
      int pc_reader;             // "pc" 有太多可能的解释了.
      int cstmr_id;              // 删减了若干字母.
      
  • 一些特定的广为人知的缩写是允许的, 例如用 i 表示迭代变量和用 T 表示模板参数

文件命名
  • 文件名要全部小写, 可以包含下划线 _连字符 -, 依照项目的约定. 如果没有约定, 那么 _ 更好.
类型命名
  • 类型名称的每个单词首字母均大写, 不包含下划线_: MyExcitingClass, MyExcitingEnum
    // 类和结构体
    class UrlTable { ...
    class UrlTableTester { ...
    struct UrlTableProperties { ...
    
    // 类型定义
    typedef hash_map<UrlTableProperties *, string> PropertiesMap;
    
    // using 别名
    using PropertiesMap = hash_map<UrlTableProperties *, string>;
    
    // 枚举
    enum UrlTableErrors { ...
    
变量命名
  • 变量 (包括函数参数) 和数据成员名一律小写, 单词之间用下划线_连接. 类的成员变量以下划线_结尾, 但结构体的就不用,如: a_local_variable, a_struct_data_member, a_class_data_member_.

    • 普通变量命名

      string table_name;  // 好 - 用下划线.
      string tablename;   // 好 - 全小写.
      
      string tableName;  // 差 - 混合大小写
      
    • 类数据成员

      • 不管是静态的还是非静态的, 类数据成员都可以和普通变量一样, 但要接下划线.
        class TableInfo {
          ...
         private:
          string table_name_;  // 好 - 后加下划线.
          string tablename_;   // 好.
          static Pool<TableInfo>* pool_;  // 好.
        };
        
  • 结构体变量

    • 不管是静态的还是非静态的, 结构体数据成员都可以和普通变量一样, 不用像类那样接下划线
      struct UrlTableProperties {
        string name;
        int num_entries;
        static Pool<UrlTableProperties>* pool;
      };
      
常量命名
  • 声明为 constexprconst 的变量, 或在程序运行期间其值始终保持不变的, 命名时以 k 开头, 大小写混合. 所有具有静态存储类型的变量 (例如静态变量或全局变量) 都应当以此方式命名.
    • 例如:
      const int kDaysInAWeek = 7;
      
宏命名
  • 全部大写,使用下划线_
函数命名
  • 常规函数使用大小写混合, 取值和设值函数则要求与变量名匹配(不强制要求)
    AddTableEntry()
    DeleteUrl()
    OpenFileOrDie()
    
命名空间命名
枚举命名
  • 与常量或宏一致
    // 两种写法都可以
    enum UrlTableErrors {
        kOK = 0,
        kErrorOutOfMemory,
        kErrorMalformedInput,
    };
    enum AlternateUrlTableErrors {
        OK = 0,
        OUT_OF_MEMORY = 1,
        MALFORMED_INPUT = 2,
    };
    

 

格式

循环和开关选择语句
  • 如果有不满足 case 条件的枚举值, switch 应该总是包含一个 default 匹配 (如果有输入值没有 case 去处理, 编译器将给出 warning). 如果 default 应该永远执行不到, 简单的加条 assert
    switch (var) {
      case 0: {  // 2 空格缩进
        ...      // 4 空格缩进
        break;
      }
      case 1: {
        ...
        break;
      }
      default: {
        assert(false);
      }
    }
    
  • 空循环体应使用 {}continue, 而不是一个简单的分号;
    for (int i = 0; i < kSomeNumber; ++i) {}  // 可 - 空循环体.
    while (condition) continue;  // 可 - contunue 表明没有逻辑.
    
条件语句
if (condition) {  // 圆括号里没有空格.
  ...  // 2 空格缩进.
} else if (...) {  // else 与 if 的右括号同一行.
  ...
} else {
  ...
}
  • 所有情况下 if(间都有个空格. ){之间也要有个空格

    if (condition) {  // 好 - IF 和 { 都与空格紧邻
    
  • 如果能增强可读性, 简短的条件语句允许写在同一行

  • 通常, 单行语句不需要使用大括号{}

  • 如果语句中某个 if-else 分支使用了大括号的话, 其它分支也必须使用(即ifelse 也要有)

命名空间格式化
  • 命名空间内不要增加额外的缩进
    namespace {
    
    void foo() {  // 正确. 命名空间内没有额外的缩进.
      ...
    }
    
    }  // namespace
    ``
    
    
水平留白
  • 通用

    • 列表初始化中大括号内的空格是可选的
      int x[] = { 0 };
      int x[] = {0};
      
    • 继承与初始化列表中的冒号前后恒有空格
      class Foo : public Bar {
       public:
        // 对于单行函数的实现, 在大括号内加上空格
        // 然后是函数实现
        Foo(int b) : Bar(), baz_(b) {}  // 大括号里面是空的话, 不加空格.
      };
      
    • 对于单行函数的实现, 在大括号内加上空格
      void Reset() { baz_ = 0; }  // 用空格把大括号与实现分开.
      
    • 添加冗余的留白会给其他人编辑时造成额外负担. 因此, 行尾不要留空格
  • 循环和条件语句

    switch (i) {
      case 1:         // switch case 的冒号前无空格.
        ...
      case 2: break;  // 如果冒号有代码, 加个空格.
    }
    
  • 操作符

    • 其它二元操作符也前后恒有空格
      v = w * x + y / z;
      
    • 对于表达式的子式可以不加空格
      v = w*x + y/z;
      
  • 模板和转换

    • 尖括号不与空格紧邻, < 前没有空格, >( 之间也没有
      vector<string> x;
      y = static_cast<char*>(x);
      
    • 在类型与指针操作符之间留空格也可以, 但要保持一致
      vector<char *> x;
      
垂直留白
  • 垂直留白越少越好

  • 两个函数定义之间的空行不要超过 2 行

  • 函数体首尾不要留空行, 函数体中也不要随意添加空行(可读性微乎其微)

函数声明与定义
  • 返回类型和函数名在同一行, 参数也尽量放在同一行, 如果放不下就对形参分行

  • 如果同一行文本太多, 放不下所有参数

    ReturnType ClassName::ReallyLongFunctionName(Type par_name1, Type par_name2,
                                                 Type par_name3) {
      DoSomething();
      ...
    }
    
  • 甚至连第一个参数都放不下(参数另起一行且缩进四格)

    ReturnType LongClassName::ReallyReallyReallyLongFunctionName(
        Type par_name1,  // 4 space indent
        Type par_name2,
        Type par_name3) {
      DoSomething();  // 2 space indent
      ...
    }
    
  • 未被使用的参数如果其用途不明显的话, 在函数定义处将参数名注释起来

    void Circle::Rotate(double /*radians*/) {}
    
  • 注意!

    • 只有在参数未被使用或者其用途非常明显时, 才能省略参数名
    • 如果返回类型与函数声明或定义分行了, 不要缩进
    • (总是和函数名在同一行
    • 函数名和(间永远没有空格
    • 圆括号与参数间没有空格
    • {总在最后一个参数同一行的末尾处, 不另起新行
    • }总是单独位于函数最后一行, 或者与{同一行
    • ){间总是有一个空格
    • 所有形参应尽可能对齐
    • 缺省缩进为 2 个空格
    • 换行后的参数保持 4 个空格的缩进
行长度
  • 每一行代码字符数不超过80

    • 如果无法在不伤害易读性的条件下进行断行, 那么注释行可以超过 80 个字符.例如, 带有命令示例或 URL 的行可以超过 80 个字符.
    • 头文件保护可以无视该原则
  • 包含长路径的 #include 语句可以超出80列

非 ASCII 字符
  • 尽量不使用非 ASCII 字符, 使用时必须使用 UTF-8 编码
空格还是制表位
  • 只使用空格,不要在代码中使用制表符,每次缩进两个空格**(Google风格)**
Lambda 表达式
  • 若用引用捕获, 在变量名和 & 之间不留空格
    auto add_to_x = [&x](int n) { x += n; };
    
函数调用
  • 类似于函数声明
列表初始化格式
  • 格式化时将将名字视作函数调用名, {} 视作函数调用的括号. 如果没有名字, 就视作名字长度为零

    • 不得不断行
      SomeFunction(
          {"assume a zero-length name before {"},  // 假设在 { 前有长度为零的名字.
          some_other_function_parameter);
      
    • 稍微短的字符串
      SomeOtherType{"Slightly shorter string",  // 稍短的字符串.
                    some, other, values}};
      
指针和引用表达式
  • 在声明指针变量或参数时, 星号与类型或变量名紧挨都可以
    // 好, 空格前置.
    char *c;
    const string &str;
    // 好, 空格后置.
    char* c;
    const string& str;
    
  • 在多重声明时不能使用&*,建议分开声明
    int x, *y;  // 不允许 - 在多重声明中不能使用 & 或 *
    
布尔表达式
  • 断行时逻辑与 && 操作符总位于行尾
    if (this_one_thing > this_other_thing &&
        a_third_thing == a_fourth_thing &&
        yet_another && last_one) {
      ...
    }
    
函数返回值
  • 不要return 表达式里加上非必须的圆括号
    return (value);                // 毕竟您从来不会写 var = (value);
    return(result);                // return 可不是函数!
    
    // 可以用圆括号把复杂表达式圈起来, 改善可读性.
    return (some_long_condition &&
            another_condition);
    
变量及数组初始化
  • 列表初始化不允许整型类型的四舍五入, 这可以用来避免一些类型上的编程失误(大括号初始化禁止内建型别之间进行隐式窄化型别转换)

    int pi(3.14);  // 好 - pi == 3.
    int pi{3.14};  // 编译错误: 缩窄转换
    
  • 非空列表初始化就会优先调用 std::initializer_list构造函数

  • 空列表{}初始化原则上会调用默认构造函数

预处理指令
  • 预处理指令不要缩进, 从行首开始
  • 非必要 - # 后跟空格
类格式
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_;
};
  • 注意

    • 所有基类名应在 80 列限制下尽量与子类名放在同一行
    • 关键词 public:, protected:, private:缩进 1 个空格
    • 除第一个关键词 (一般是 public) 外, 其他关键词前要空一行. 如果类比较小的话也可以不空
构造函数初始值列表
  • 构造函数初始化列表放在同一行或按四格缩进并排多行.
    // 如果所有变量能放在同一行:
    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) {}
    
     

头文件

#define保护
  • 所有头文件都应该使用#define来防止头文件被多重包含,命名格式<PROJECT>_<PATH>_<FILE>_H_

    • 为保证唯一性, 头文件的命名应该基于所在项目"源代码树"的全路径。如项目foo中的头文件foo/src/bar/baz.h可以按此方式保护
      #ifndef FOO_BAR_BAZ_H_
      #define FOO_BAR_BAZ_H_
      ...
      #endif // FOO_BAR_BAZ_H_
      
前置声明
  • 定义:指类、函数和模板的存粹声明,没伴随着其定义

  • 尽可能地避免使用前置声明那些定义在其他项目中的实体,使用#include包含需要的头文件即可

内联函数
  • 相关内联函数的性能差异,可参考[Effective C++ 条款2]

  • 只有当函数只有10行甚至更少时才将其定义为内联函数

  • 内联那些包含循环或switch语句的函数常常得不偿失

  • 有些函数即使声明为内联的也不一定会被编译器内联(只是个请求)

  • 类内部的函数一般会自动内联,所以某函一旦不需要内联就应该放到对应的.cc文件里,这样可以保持头文件的类相当精炼,也很好地贯彻了声明与定义分离的原则

#include的路径及顺序
  • 项目内头文件应按照项目源代码目录树结构排列

  • 包含头文件的次序

    1. cpp对应的头文件
    2. C系统文件
    3. C++系统文件
    4. 其他库的.h文件
    5. 本项目内的.h文件
       

注释

注释风格
  • ///* */ 都可以; 但 // 更 常用. 要在如何注释及注释风格上确保统一.
文件注释
  • 在每一个文件开头加入版权公告

  • 文件注释描述了该文件的内容. 如果一个文件只声明, 或实现, 或测试了一个对象, 并且这个对象已经在它的声明处进行了详细的注释, 那么就没必要再加上文件注释. 除此之外的其他文件都需要文件注释.

  • 法律公告和作者信息

    • 每个文件都应该包含许可证引用. 为项目选择合适的许可证版本
    • 如果你对原始作者的文件做了重大修改, 请考虑删除原作者信息
  • 文件内容

    • 如果一个 .h 文件声明了多个概念, 则文件注释应当对文件的内容做一个大致的说明, 同时说明各概念之间的联系. 一个一到两行的文件注释就足够了, 对于每个概念的详细文档应当放在各个概念中, 而不是文件注释中.
类注释
  • 每个类的定义都要附带一份注释, 描述类的功能和用法, 除非它的功能相当明显

    // 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 和 .c 文件中), 此时, 描述类用法的注释应当和接口定义放在一起, 描述类的操作和实现的注释应当和实现放在一起.

函数注释
  • 函数声明处的注释描述函数功能; 定义处的注释描述函数实现.

  • 函数声明处注释的内容

    • 函数的输入输出.
    • 对类成员函数而言: 函数调用期间对象是否需要保持引用参数, 是否会释放这些参数.
    • 函数是否分配了必须由调用者释放的空间.
    • 参数是否可以为空指针.
    • 是否存在函数使用上的性能隐患.
    • 如果函数是可重入的, 其同步前提是什么?
  • 函数重载时

    • 注释的重点应该是函数中被重载的部分
  • 函数定义

    • 如果函数的实现过程中用到了很巧妙的方式, 那么在函数定义处应当加上解释性的注释.

      • 例如, 你所使用的编程技巧, 实现的大致步骤, 或解释如此实现的理由. 举个例子, 你可以说明为什么函数的前半部分要加锁而后半部分不需要
    • 不要 从 .h 文件或其他地方的函数声明处直接复制注释. 简要重述函数功能是可以的, 但注释重点要放在如何实现上

变量注释
  • 类数据成员

    • 每个类数据成员 (也叫实例变量或成员变量) 都应该用注释说明用途
    • 如果有非变量的参数(例如特殊值, 数据成员之间的关系, 生命周期等)不能够用类型与变量名明确表达, 则应当加上注释
    • 如果变量类型与变量名已经足以描述一个变量, 那么就不再需要加上注释.
    • 特别地, 如果变量可以接受 NULL 或 -1 等警戒值, 须加以说明
      private:
       // Used to bounds-check table accesses. -1 means
       // that we don't yet know how many entries the table has.
       int num_total_entries_;
      
  • 全局变量

    • 注释说明含义及用途, 以及作为全局变量的原因
      // The total number of tests cases that we run through in this regression test.
      const int kNumTestCases = 6;
      
实现注释
  • 代码前注释

    • 巧妙或复杂的代码段前要加注释
      // 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;
      }
      
  • 行注释

    • 比较隐晦的地方要在行尾加入注释. 在行尾空两格进行注释即 //
      // If we have enough memory, mmap the data portion too.
      mmap_budget = max<int64>(0, mmap_budget - index_->length());
      if (mmap_budget >= data_size_ && !MmapData(mmap_chunk_bytes, mlock))
        return;  // Error already logged.
      
    • 如果你需要连续进行多行注释, 可以使之“对齐"获得更好的可读性
标点, 拼写和语法
  • 注释的通常写法是包含正确大小写和结尾句号的完整叙述性语句

  • 完整的句子比句子片段可读性更高

  • 短一点的注释, 比如代码行尾注释, 可以随意点, 但依然要注意风格的一致性

TODO 注释
  • 标记下次我们需要做的工作

  • TODO 注释要使用全大写的字符串 TODO, 在随后的圆括号里写上你的名字, 邮件地址, bug ID, 或其它身份标识和与这一 TODO 相关的 issue

  • 主要目的是让添加注释的人 (也是可以请求提供更多细节的人) 可根据规范的 TODO 格式进行查找. 添加 TODO 注释并不意味着你要自己来修正
     

作用域

命名空间
  • 禁止使用内联命名空间

  • cpp中的匿名命名空间可避免命名冲突, 限定作用域, 避免直接使用 using 关键字污染命名空间

  • 作用域的使用, 除了考虑名称污染, 可读性之外, 主要是为降低耦合, 提高编译/执行效率

局部变量
  • 如果变量是一个对象, 每次进入作用域都要调用其构造函数, 每次退出作用域都要调用其析构函数. 这会导致效率降低.

    // 低效的实现
    for (int i = 0; i < 1000000; ++i) {
        Foo f;                  // 构造函数和析构函数分别调用 1000000 次!
        f.DoSomething(i);
    }
    
    • 在循环作用域外面声明这类变量要高效的多
      Foo f; 
      for (int i = 0; i < 1000000; ++i) {
          f.DoSomething(i);
      }
      
  • 尽量不用全局函数和全局变量, 考虑作用域和命名空间限制, 尽量单独形成编译单元

  • 多线程中的全局变量 (含静态成员变量) 不要使用 class 类型 (含 STL 容器), 避免不明确行为导致的 bug
     

函数

参数顺序
  • 输入参数是值参或 const 引用, 输出参数为指针

  • 函数的参数顺序为: 输入参数在先, 后跟输出参数

    int func(int a , int *p)
    {
        *p = a +10;
        return 0;
    }
    
编写简短函数
  • 如果函数超过 40 行, 可以思索一下能不能在不影响程序结构的前提下对其进行分割
引用参数
  • 所有按引用传递的参数必须加上 const
    void Foo(const string &in, string *out);
    
缺省参数
  • 只允许在非虚函数中使用缺省参数, 对于虚函数, 不允许使用缺省参数,且必须保证缺省参数的值始终一致
函数返回类型后置语法
  • 只有在常规写法 (返回类型前置) 不便于书写或不便于阅读时使用返回类型后置语法.
     

构造函数
  • 不要在构造函数中调用虚函数

  • 不要在无法报错时进行可能失败的初始化

隐式类型转换
  • 不要定义隐式类型转换

  • 对于转换运算符和单参数构造函数, 请使用 explicit 关键字

    class Foo {
      explicit Foo(int x, double y);  // 禁止发生隐式转换
    ...
    };
    void Func(Foo f);
    
  • 拷贝和移动构造函数不应当被标记为 explicit,因为它们并不执行类型转换

可拷贝类型和可移动类型
  • 如果你的类不需要拷贝 / 移动操作, 请显式地通过在 public 域中使用 = delete 或其他手段禁用之. [Effective C++ 条款6]
    // MyClass is neither copyable nor movable.
    MyClass(const MyClass&) = delete;
    MyClass& operator=(const MyClass&) = delete;
    
结构体
  • 仅当只有数据成员时使用 struct, 其它一概使用 class

  • 为了和 STL 保持一致, 对于仿函数等特性可以不用 class 而是使用 struct

继承
  • 以下约定俗成可参看Effective C++

  • 使用组合,GOF在设计模式中强调的,常常比使用继承更合理

  • "is a"使用public继承

  • "has a"使用组合

  • "is implement in items of"使用private或者组合

  • 有虚成员函数或者多态时使用虚析构函数

  • 对于重载的虚函数或虚析构函数, 使用 override, 或 (较不常用的) final 关键字显式地进行标记, 用于判定该函数是否是虚函数

  • final关键字:禁止类被继承,禁止虚函数被重写

  • override关键字:声明在子类中重写的虚函数

多重继承
  • 只有当所有父类除第一个外都是 纯接口类 时, 才允许使用多重继承
接口
  • 满足特定条件的类

    • 纯接口

      1. 只有纯虚函数 (“=0”) 和静态函数 (除了下文提到的析构函数).
      2. 没有非静态数据成员.
      3. 没有定义任何构造函数. 如果有, 也不能带有参数, 并且必须为 protected.
      4. 如果它是一个子类, 也只能从满足上述条件并以 Interface 为后缀的类继承.
  • 接口类不能被直接实例化, 因为它声明了纯虚函数,为确保接口类的所有实现可被正确销毁, 必须为之声明虚析构函数

运算符重载
  • 除少数特定环境外, 不要重载运算符. 也不要创建用户定义字面量
存取控制
  • 将所有数据成员声明为 private, 除非是 static const 类型成员 (遵循 常量命名规则). 处于技术上的原因, 在使用 Google Test 时我们允许测试固件类中的数据成员为 protected
声明顺序
  • 类定义一般应以 public: 开始, 后跟 protected:, 最后是 private:. 省略空部分.

  • 在各个部分中, 建议将类似的声明放在一起, 并且建议以如下的顺序:

    1. 类型 (包括 typedef, using 和嵌套的结构体与类)
    2. 常量(如enum)
    3. 工厂函数(虚函数)
    4. 构造函数
    5. 赋值运算符
    6. 析构函数
    7. 其它函数
    8. 数据成员
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值