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)

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

水平留白

  • 不要加入多余的空格

垂直留白

  • 垂直留白越少越好

热门文章

暂无图片
编程学习 ·

那些年让我们目瞪口呆的bug

程序员一生与bug奋战&#xff0c;可谓是杀敌无数&#xff0c;见怪不怪了&#xff01;在某知识社交平台中&#xff0c;一个“有哪些让程序员目瞪口呆的bug”的话题引来了6700多万的阅读&#xff0c;可见程序员们对一个话题的敏感度有多高。 1、麻省理工“只能发500英里的邮件” …
暂无图片
编程学习 ·

redis的下载与安装

下载redis wget http://download.redis.io/releases/redis-5.0.0.tar.gz解压redis tar -zxvf redis-5.0.0.tar.gz编译 make安装 make install快链方便进入redis ln -s redis-5.0.0 redis
暂无图片
编程学习 ·

《大话数据结构》第三章学习笔记--线性表(一)

线性表的定义 线性表&#xff1a;零个或多个数据元素的有限序列。 线性表元素的个数n定义为线性表的长度。n为0时&#xff0c;为空表。 在比较复杂的线性表中&#xff0c;一个数据元素可以由若干个数据项组成。 线性表的存储结构 顺序存储结构 可以用C语言中的一维数组来…
暂无图片
编程学习 ·

对象的扩展

文章目录对象的扩展属性的简洁表示法属性名表达式方法的name属性属性的可枚举性和遍历可枚举性属性的遍历super关键字对象的扩展运算符解构赋值扩展运算符AggregateError错误对象对象的扩展 属性的简洁表示法 const foo bar; const baz {foo}; baz // {foo: "bar"…
暂无图片
编程学习 ·

让程序员最头疼的5种编程语言

世界上的编程语言&#xff0c;按照其应用领域&#xff0c;可以粗略地分成三类。 有的语言是多面手&#xff0c;在很多不同的领域都能派上用场。大家学过的编程语言很多都属于这一类&#xff0c;比如说 C&#xff0c;Java&#xff0c; Python。 有的语言专注于某一特定的领域&…
暂无图片
编程学习 ·

写论文注意事项

参考链接 给研究生修改了一篇论文后&#xff0c;该985博导几近崩溃…… 重点分析 摘要与结论几乎重合 这一条是我见过研究生论文中最常出现的事情&#xff0c;很多情况下&#xff0c;他们论文中摘要部分与结论部分重复率超过70%。对于摘要而言&#xff0c;首先要用一小句话引…
暂无图片
编程学习 ·

安卓 串口开发

上图&#xff1a; 上码&#xff1a; 在APP grable添加 // 串口 需要配合在项目build.gradle中的repositories添加 maven {url "https://jitpack.io" }implementation com.github.licheedev.Android-SerialPort-API:serialport:1.0.1implementation com.jakewhart…
暂无图片
编程学习 ·

2021-2027年中国铪市场调研与发展趋势分析报告

2021-2027年中国铪市场调研与发展趋势分析报告 本报告研究中国市场铪的生产、消费及进出口情况&#xff0c;重点关注在中国市场扮演重要角色的全球及本土铪生产商&#xff0c;呈现这些厂商在中国市场的铪销量、收入、价格、毛利率、市场份额等关键指标。此外&#xff0c;针对…
暂无图片
编程学习 ·

Aggressive cows题目翻译

描述&#xff1a; Farmer John has built a new long barn, with N (2 < N < 100,000) stalls.&#xff08;John农民已经新建了一个长畜棚带有N&#xff08;2<N<100000&#xff09;个牛棚&#xff09; The stalls are located along a straight line at positions…
暂无图片
编程学习 ·

剖析组建PMO的6个大坑︱PMO深度实践

随着事业环境因素的不断纷繁演进&#xff0c;项目时代正在悄悄来临。设立项目经理转岗、要求PMP等项目管理证书已是基操&#xff0c;越来越多的组织开始组建PMO团队&#xff0c;大有曾经公司纷纷建造中台的气质&#xff08;当然两者的本质并不相同&#xff0c;只是说明这个趋势…
暂无图片
编程学习 ·

Flowable入门系列文章118 - 进程实例 07

1、获取流程实例的变量 GET运行时/进程实例/ {processInstanceId} /变量/ {变量名} 表1.获取流程实例的变量 - URL参数 参数需要值描述processInstanceId是串将流程实例的id添加到变量中。变量名是串要获取的变量的名称。 表2.获取流程实例的变量 - 响应代码 响应码描述200指…
暂无图片
编程学习 ·

微信每天自动给女[男]朋友发早安和土味情话

微信通知&#xff0c;每天给女朋友发早安、情话、诗句、天气信息等~ 前言 之前逛GitHub的时候发现了一个自动签到的小工具&#xff0c;b站、掘金等都可以&#xff0c;我看了下源码发现也是很简洁&#xff0c;也尝试用了一下&#xff0c;配置也都很简单&#xff0c;主要是他有一…
暂无图片
编程学习 ·

C语言二分查找详解

二分查找是一种知名度很高的查找算法&#xff0c;在对有序数列进行查找时效率远高于传统的顺序查找。 下面这张动图对比了二者的效率差距。 二分查找的基本思想就是通过把目标数和当前数列的中间数进行比较&#xff0c;从而确定目标数是在中间数的左边还是右边&#xff0c;将查…
暂无图片
编程学习 ·

项目经理,你有什么优势吗?

大侠被一个问题问住了&#xff1a;你和别人比&#xff0c;你的优势是什么呢? 大侠听到这个问题后&#xff0c;脱口而出道&#xff1a;“项目管理能力和经验啊。” 听者抬头看了一下大侠&#xff0c;显然听者对大侠的这个回答不是很满意&#xff0c;但也没有继续追问。 大侠回家…
暂无图片
编程学习 ·

nginx的负载均衡和故障转移

#注&#xff1a;proxy_temp_path和proxy_cache_path指定的路径必须在同一分区 proxy_temp_path /data0/proxy_temp_dir; #设置Web缓存区名称为cache_one&#xff0c;内存缓存空间大小为200MB&#xff0c;1天没有被访问的内容自动清除&#xff0c;硬盘缓存空间大小为30GB。 pro…
暂无图片
编程学习 ·

业务逻辑漏洞

身份认证安全 绕过身份认证的几种方法 暴力破解 测试方法∶在没有验证码限制或者一次验证码可以多次使用的地方&#xff0c;可以分为以下几种情况︰ (1)爆破用户名。当输入的用户名不存在时&#xff0c;会显示请输入正确用户名&#xff0c;或者用户名不存在 (2)已知用户名。…