CPP & 笔记 - Google C++ 编程规范

2021/6/26 14:56:59

本文主要是介绍CPP & 笔记 - Google C++ 编程规范,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

头文件

#define<PROJECT>_<PATH>_FILE_H_

头文件依赖

  • 使用 前置声明(forward declarations) 尽量减少 .h 文件中 #include 的数量

    • 头文件中用到类 File,但不需要访问 File 的声明

      • 则头文件中只需 前置声明 class File;

      • 无需 #include "file.h"

包含头文件的名称及次序

  • 名称要求

    • 按照项目源代码目录树结构排序,并且避免使用 UNIX 文件路径:.(当前目录)和 …(父目录)
  • 次序

    1. 本项目头文件

    2. C 系统文件

    3. C++ 系统文件

    4. 其他库头文件

    5. 本项目内头文件

内联函数(inline function)

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

  • 所有的内联函数的定义应放在 .h 文件中

    • 如果内联函数的定义比较短小、逻辑比较简单,其实现代码可以放在 .h 文件中

    • 较复杂的内联函数也可以放到 .h 文件中,或将其分离到单独的 -inl.h

函数参数顺序(Function Parameter Ordering)

  • 输入参数在前,输出参数在后

    • 输入参数 一般 传值常数引用(const references)

    • 输出参数 或 输入/输出参数 为 非常数指针

作用域

命名空间

提倡使用 不具名的命名空间(unnamed namespaces)
不要声明命名空间 std 下的任何内容,包括标准库类的 前置声明

不具名命名空间(Unnamed Namespaces)

具名命名空间(Named Namespaces)

命名空间将 **除文件包含、全局标识的声明/定义 以及 类的前置声明** 外的整个源文件封装起来

以同其他命名空间相区分

嵌套类 / 成员类

  • 不要将嵌套类定义为 public,除非它们是接口的一部分

非成员函数(Nonmember)、静态成员函数(Static Member)和全局函数(Global Functions)

  • 使用命名空间中的 非成员函数静态成员函数,尽量不要使用 全局函数

局部变量

  • 将函数变量尽可能置于 最小作用域内,在声明变量时将其初始化

全局变量(Global Variables)

  • 禁止使用 class 类型 的全局变量(包括 STL 的 string, vector 等等)

    • 一定要使用的话,请使用 单例模式(singleton pattern)
  • 全局的 字符串常量,使用 C 风格 的字符串,而不要使用 STL 的字符串

    const charkFrogSays[] = "ribbet";

构造函数(Constructor)的职责

  • 构造函数中只进行那些没有实际意义的(trivial)初始化

  • 可能的话,使用 Init() 方法 几种初始化 **有意义的(non-trivial)**数据

默认构造函数(Default Constructors)

  • 如果 定义了若干成员变量,但 没有其他构造函数,则自定义一个默认构造函数

    • 编译器自动生产的默认构造函数并不会对对象进行初始化

明确的构造函数(Explicit Constructors)

  • 对单参数构造函数使用 C++ 关键字 explicit

    • 避免隐式转换

拷贝构造函数(Copy Constructors) 和 赋值操作(assignment operator)

  • 仅在代码中需要拷贝一个类对象的时候使用拷贝构造函数

  • 不需要拷贝时,应使用 DISALLOW_COPY_AND_ASSIGN

    • 避免编译器自动生成相应的 public 的拷贝构造函数

    • 可能会是导致 性能问题bugs 的根源,并且降低了 代码可读性(相比按引用传递,跟踪按值传递的对象更加困难)

#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
  TypeName(const TypeName&); \
  void operator=(const TypeName&)
  
class Foo {
public:
  Foo(int f);
  ~Foo();

private:
  DISALLOW_COPY_AND_ASSIGN(Foo);
}; 

结构体和类(Structs vs. Classes)

  • 仅当只有数据时,使用 struct,其它一概使用 class
    • struct 被用在仅包含数据的消极对象(passive objects)
      • 可能包括有关联的常量,但没有存取数据成员之外的函数功能
      • 而存取功能通过直接访问实现而无需方法调用
      • 如:构造函数、析构函数、Initialize()、Reset()、Validate()
    • 如果与 STL 结合,对于 仿函数(functors)和特性(traits) 也可使用 struct

继承(Inheritance)

  • 使用 组合(composition)(“is-a”) 通常比使用 继承(“has-a”) 更适宜
  • 如果使用继承的话,只使用公共继承
  • C++ 实践中,继承主要用于两种场合
    1. 实现继承(implementation inheritance),子类继承父类的实现代码
    2. 接口继承(interface inheritance),子类仅继承父类的方法名称

多重继承(Multiple Inheritance)

  • 只有当最多一个基类中 含有实现,其他基类都是以 Interface 为后缀的纯接口类时,才会使用多重继承
    • 为确保其余基类都是纯接口,必须以 Interface 为后缀

接口(Interface)

  • 当一个类满足以下要求时,称之为纯接口
    1. 只有 纯虚函数("=0")静态函数析构函数 除外
    2. 没有非静态数据成员
    3. 没有定义任何构造函数。如果有,也不含参数,并且为 protected
    4. 如果是子类,也只能继承满足上述条件并以 Interface 为后缀的类
  • 为确保接口类的所有实现可被正确销毁,必须为之声明 虚析构函数

操作符重载(Operator Overloading)

  • 除少数特定环境外,不要重载 操作符
  • 如果有需要的话,可以定义类似 Equals()、CopyFrom() 等函数

存取控制(Access Control)

  • 将数据成员私有化,并提供相关存取函数
    • 变量 foo_、取值函数 foo()、赋值函数 set_foo()

声明次序(Declaration Order)

  • 类中的声明次序:public: ==> protected: ==> private:
  • 每一块中,声明次序如下
    1. typedefsenums
    2. 常量
    3. 构造函数
    4. 析构函数
    5. 成员函数,含静态成员函数
    6. 数据成员,含静态数据成员
  • DISALLOW_COPY_AND_ASSIGN 置于 private: 块之后,作为类的最后部分
  • .cc 文件中,函数的定义 应尽可能和 声明次序 一直

智能指针

  • 任何情况下都不要使用 auto_ptr,使用 scoped_ptrshared_ptr

其他 C++ 特性

引用参数(Reference Arguments)

  • 函数形参表中,所有按引用传递的参数必须加上 const

    void Foo(const string &in, string *out);

函数重载(Function Overloading)

  • 仅在 输入参数不同、功能相同 时使用重载函数(含构造函数)
  • 不要使用函数重载模仿 缺省函数参数
  • 如果想要重载一个函数,实现不同的功能,考虑让函数名包含参数信息。如:AppendString() 和 AppendInt()

缺省参数(Default Arguments)

  • 禁止使用缺省函数参数
  • 所有参数必须在使用时明确指定

变长数组 和 alloca(Variable-Length Arrays and alloca())

  • 禁止使用变长数组 和 alloca()
  • 使用安全的 分配器(allocator)

友元(Friends)

  • 允许合理使用 友元类友元函数

异常(Exceptions)

  • 禁止使用 C++ 异常

运行时类型识别(Run-Time Type Information,RTTI)

  • 禁止使用 RTTI
  • 虚函数 可以实现 随子类类型不同而执行不同代码,工作都是交给对象本身去完成
  • 如果工作在对象之外的代码中完成,考虑 双重分发方案,如 Visitor 模式,可以方便的在对象本身之外确定类的类型

类型转换(Casting)

  • 使用 static_cat<>() 等 C++ 的类型转换
    1. static_cast:实现 值的强制转换指针的父类到子类的明确的向上转换
    2. const_cast:移除 const 属性
    3. reinterpret_cast:指针类型 和 整型 或 其他指针间 不安全的相互转换
    4. dynamic_cast:除测试外不要使用,如果需要在运行时确定类型信息,说明设计有缺陷

流(Streams)

  • 只有在 记录日志 时使用流
  • 流最大的优势是在输出时不需要关心输出对象的类型,这是一个亮点,也是一个不足
    • 容易用错类型,而编译器不会报警
  • 使用 printf + read/write

前置自增和自减(Preincrement and Predecrement)

  • 对于 迭代器 和 其他模板对象 使用前缀形式(++i)的自增、自减运算符
  • 简单数值(非对象) 都可

const 的使用(Use of const)

  • 在任何可以使用的情况下都要使用 const
    1. 如果函数不会修改传入的 引用 或 指针类型 的参数,这样的参数应该为 const
    2. 尽可能将 函数 声明为 const,访问函数 应该总是 const
    3. 其他函数如果 不会修改任何数据成员 也应该是 const,不要调用 非 const 函数,不要返回 对数据成员的非 const 指针 或 引用
    4. 如果 数据成员对象构造 之后不再改变,可将其定义为 const

整型(Integer Type)

  • C++ 内建整型中,只使用 int
  • 如果程序中需要不同大小的变量,可以使用 <stdint.h> 中的 精确宽度(precise-width) 的整型,如 int16_t, int64_t
  • 禁止使用 uint32_t 等无符号整型
    • 如果数值不会为负值,使用 断言(assertion) 来保护数据
    • 无符号整型可能会导致一些数值上的 bug,以及 类型提升机制(type-promotion scheme) 会致使无符号类型的行为出乎意料

64 位下的可移植性(64-bit Portability)

  1. printf() 的部分格式需要使用宏进行自定义
  2. *sizeof(void ) != sizeof(int)
  3. 注意 结构对齐
  4. 创建 64 位常量时,使用 LL 或 ULL 作为后缀,如
    • int64_t my_value = 0x123456789LL;
    • uint64_t my_mask = 3ULL << 48
  5. 如果确实需要 32 位和 64 位系统具有不同代码,可以在代码变量前使用

预处理宏(Preprocessor Macros)

  • 尽量以 内联函数、枚举 和 常量 代替之
  • 宏的高级应用,参考 C 语言宏的高级应用
  • 参考信息
    1. 不要在 .h 文件中定义宏
    2. 使用前正确 #define,使用后正确 undef
    3. 不要只是对已经存在的宏使用 #undef,选择一个不会冲突的名称
    4. 不适用会导致不稳定的 C++ 构造(unbalanced C++ constructs)的宏

0 和 NULL(0 and NULL)

  • 整数用 0,实数用 0.0,指针用 NULL,字符(串)用 ‘\0’

sizeof(sizeof)

  • 尽可能使用 sizeof(varname) 代替 sizeof(type)

Boost 库(Boost)

命名约定

通用命名规则(General Naming Rules)

函数命名、变量命名、文件命名应具有描述性,不要过度缩写

类型和变量 应该是名词,函数名 可以用"命令性"动词

文件命名(File Name)

  1. C++ 文件以 .cc 结尾,头文件以 .h 结尾
  2. 内联函数放在 .h 文件内,或单独放到以 -inl.h 结尾的文件中
  3. 文件名全部小写,包含下划线

类型命名(Type Name)

  1. 类、结构体、类型定义、枚举,都使用此约定
  2. 每个单词以大写字母开头,不包含下划线

变量命名(Variable Names)

  1. 类的成员变量以下划线结尾
  2. 全局变量以 g_ 为前缀
  3. 变量名一律小写,包含下划线

常量命名(Constant Names)

  1. 常量以 k 为前缀
  2. 每个单词以大写字母开头,不包含下划线

函数命名(Function Names)

普通函数(regular functions)

- 每个单词以大写字母开头,不包含下划线

存取函数(accessors and mutators)

- 与变量名匹配
class MyClass {
public:
  int num_entries() { return num_entries; }
  void set_num_entries(int num_entries) { num_entries_ = num_entries; }

private:
  int num_entries_;
};

命名空间

  • 命名空间的名称一律小写,包含下划线

枚举命名(Enumerator Names)

枚举名称

- 每个单词以大写字母开头,不包含下划线

枚举值

- 全部大写,单词间以下划线相连

宏命名(Macro Names)

  • 全部大写,单词间以下划线相连

格式

行长度(Line Length)

  • 每一行代码字符数不超过 80

非 ASCII 字符(Non-ASCII Characters)

  • 使用 UTF-8 格式

空格还是制表位(Spaces vs. Tabs)

  • 只使用空格
  • 每次缩进 2 个空格

函数声明与定义(Functions Declarations and Definitions)

  • 返回类型和函数名在同一行,合适的话,参数也放在同一行
  • 注意点
    1. 返回值总是和函数名在同一行;
    2. 左圆括号(openparenthesis)总是和函数名在同一行;
    3. 函数名和左圆括号间没有空格;
    4. 圆括号与参数间没有空格;
    5. 左大括号(opencurlybrace)总在最后一个参数同一行的末尾处;
    6. 右大括号(closecurlybrace)总是单独位于函数最后一行;
    7. 右圆括号(closeparenthesis)和左大括号间总是有一个空格;
    8. 函数声明和实现处的所有形参名称必须保持一致;
    9. 所有形参应尽可能对齐;
    10. 缺省缩进为 2 个空格;
    11. 独立封装的参数保持 4 个空格的缩进。

函数调用(Functions Calls)

  • 尽量放在同一行,否则将实参封装在圆括号中

条件语句(Conditionals)

  • 不在圆括号中添加空格,关键字 else 另起一行
  • if 和 左圆括号 间有个空格,右圆括号 和 左大括号 间也有个空格
  • 语句简单 并且没有使用 else 子句 时,可以将 条件语句写在同一行

循环和开关选择语句(Loops and Switch Statements)

  • switch 语句中的 default 永不会执行,可以简单的使用 assertdefault: assert(false);
  • 空循环体应使用 {} 或 continue

指针和引用表达式

  • 句点 . 或 箭头 -> 前后不要有空格
  • 指针 * 或 地址操作符 & 后不要有空格

布尔表达式(Boolean Expressions)

  • 逻辑与 && 操作符总位于行尾

函数返回值(Return Values)

  • return 表达式中不要使用 圆括号

变量及数组初始化

  • = 或者 () 都可

预处理指令(Preprocessor Directives)

  • 预处理指令不要缩进,从行首开始

类格式(Class Format)

  • 声明属性依次序是 public: ==> protected: ==> private:,每次缩进 1 个空格
    1. 所以基类名应在 80 列限制下尽量与子类名放在同一行;
    2. 关键词 public:、 protected:、 private:要缩进 1 个空格
    3. 除第一个关键词(一般是 public)外,其他关键词前空一行,如果类比较小的话也可以不空;
    4. 这些关键词后不要空行;
    5. public 放在最前面,然后是 protected 和 private;
    6. 关于声明次序参考第三篇声明次序一节

初始化列表(Initializer Lists)

  • 构造函数初始化列表放在 同一行 或 四个缩进并排几行

命名空间格式化(Namespace Formatting)

  • 命名空间内容不添加额外缩进层次

水平留白

  • 不要加入多余的空格

垂直留白

  • 垂直留白越少越好


这篇关于CPP & 笔记 - Google C++ 编程规范的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程