C/C++ 编码规范

C/C++ 编码规范
程序员的职业素养
编码是一门手艺,我们都是匠人
请对代码怀有敬畏之心
编码是一种责任
请对你自己写的代码负责
请在你的作品上署名,并郑重的承诺——为了这段代码我已用尽我的全力
请不要编写你自己不懂的代码
我们允许你寻求帮助,允许你拷贝
但——
在你弄懂你写下的代码的实际含义之前,请不要把它合并到你的作品中去
我,要做一名有素养的程序员
目录
程序员的职业素养

  1. 头文件
    [红] 1.1 新增模块的头文件必须自给自足
    [红] 1.2 头文件必须使用宏或预编译指令进行保护,防止嵌套引用
    [黄] 1.3 尽量避免使用前置声明
    [黄] 1.4 尽量避免过早使用内联
    [黄] 1.5 C++中,使用C标准库时,使用不带.h后缀的头文件
    [黄] 1.6 使用标准的头文件引入顺序
    [黄] 1.7 考虑使用纯虚接口类或者PIMPL构建编译防火墙
    [黄] 1.8 使用不完整类型隐藏 C 语言结构体的实现细节
    [红] 1.9 禁止在头文件中使用 using namespace xxx;

  2. [红] 2.1 尽可能少用宏定义
  3. 作用域
    [黄] 3.1 所有自定义的符号都最好放入到某个 namespace 下面
    [黄] 3.2 推荐使用匿名 namespace 替代 static 声明文件内部可见的符号
    [红] 3.3 禁止在 std 名字空间下定义任何东西
    [黄] 3.4 用非成员非友元函数取代成员函数
    [黄] 3.5 尽可能缩小局部变量的作用域
    [红] 3.6 不要在switch、if…else…、if…else if…else…代码块中,反复声明某个结构体,数组,类等的临时变量,应将其提出到语句块的外层
    [黄] 3.7 使用 RAII 管理资源的生命周期

  4. [红] 4.1 禁止在基类的构造函数和析构函数中调用虚函数
    [红] 4.2 禁止在析构函数中抛异常
    [黄] 4.3 尽量避免隐式转换
    [黄] 4.4 考虑编写或者禁用拷贝构造
    [黄] 4.5 拷贝构造,赋值操作符,析构函数最好同时定义
    [黄] 4.6 不要在 struct 中定义成员变量以外的东西
    [黄] 4.7 能用组合的地方,尽量少用继承
    [红] 4.8 所有接口类的成员变量应定义为 private
    [红] 4.9 使用统一的成员声明顺序
    [黄] 4.10 尽量不要重载操作符
    [红] 4.11 基类的析构函数必须是 public virtual 或 protected non virtual
  5. 函数
    [黄] 5.1 优先使用 const 引用传递参数
    [黄] 5.2 尽量减少输出参数
    [黄] 5.3 输入参数放在输出参数前面
    [黄] 5.4 函数最好不要超过 40 行
    [红] 5.5 统一输出参数的传递方式
    [黄] 5.6 使用函数重载时,确保它们的逻辑含义是一样的
    [黄] 5.7 考虑使用提前返回的方式减少嵌套
  6. 命名
    [黄] 6.1 通用命名规则
    [红] 6.2 文件命名
    [黄] 6.3 类型命名
    [黄] 6.4 变量命名
    [黄] 6.5 常量命名
    [黄] 6.6 全局变量命名
    [黄] 6.7 函数命名
    [黄] 6.8 命名空间命名
    [红] 6.9 不要在命名空间中嵌套一些已经有的知名的命名空间
    [红] 6.10 枚举命名和常量一样命名
    [红] 6.11 如果变量名中含有变量类型信息(我们不推荐这么做,但我们的供应商代码中难免有此类情况),那么变量类型一定要和变量命名中的类型信息一致
    [红] 6.12 如果使用位域,请不要在变量命中包含变量类型,而应该说明用途
  7. 注释
    [黄] 7.1 注释风格
    [红] 7.2 类注释
    [黄] 7.3 函数注释
    [黄] 7.4 实现注释
    [黄] 7.5 变量注释
    [黄] 7.6 实参注释
    [黄] 7.7 形参注释
    [黄] 7.8 版权注释
  8. 格式
  9. 其他
    [黄] 9.1 尽量使用智能指针替代裸指针
    [黄] 9.2 避免使用 RTTI 和 dynamic_cast
    [黄] 9.3 C++中,避免使用C式类型强转
    [黄] 9.4 C中,优先使用i而不是i++
    [黄] 9.5 定义常量时考虑使用const替代宏
    [黄] 9.6 C++中,慎用宏定义
    [黄] 9.7 优先使用 nullptr 替代 NULL,0
    [黄] 9.8 优先使用sizeof(val)而不是sizeof(type)
    [黄] 9.9 C++中,优先使用 using 替代 typedef 定义别名
    [黄] 9.10 优先使用标准库中的算法替代手写算法
    [黄] 9.11 避免在代码中出现魔数或硬编码
    [黄] 9.12 避免在代码中执行命令
    [黄] 9.13 避免交换条件表达式的参数
    [黄] 9.14 不要轻易忽略任何一个警告
  10. 宝箱
    [红] 10.1 流程控制语句间一定要用{}包含处理语句块,哪怕只有一行语句
    [红] 10.2 复杂表达式,要用()明确表示优先级
    [红] 10.3 禁止使用不相关的表达式或变量做if else的流程选择
    [黄] 10.4 避免滥用extern来声明函数或变量,在函数内部使用extern或在同层使用extern
    [红] 10.5 不要在代码中保留“赤裸裸的”printf,cout等打印语句
    [红] 10.6 switch语句,无论何时,请写上default语句
  11. 金玉良言
    11.0 请不要编写你自己不懂的代码,我们允许你寻求帮助,允许你拷贝,但是在你弄懂你写下的代码的实际含义之前,请不要把它合并到你的项目工程中去
    11.1 代码修改的log,请遵守log规范。因为最有可能查询这条记录的人——是你自己
    11.2 一些耗时操作不能放到while循环里面或者执行频率高的地方,这极有可能造成系统卡顿
    11.3 写操作不要放到while循环或者执行判断高的地方,防止造成器件损坏或者其他进程或者函数无法再重写
    11.4 用户功能和工厂功能要完整的隔离,互相不能影响
    11.5 如果模块的子任务间是串行的,即上一个任务必须达到某个条件时,才能进入下一个任务,请设计为状态机
    11.6 中断里的过程变量不允许在中断外使用
    编者
    审核
    修订
    致歉
  12. 头文件
    [红] 1.1 新增模块的头文件必须自给自足
    头文件用到的所有的符号需要引入对应的头文件或者使用前置声明。一种检验方式是这个头文件可以单独编译通过:g++ -c header.h。另一种检验方式是这个头文件可以作为第一个头文件引用。
    BAD
    #ifndef CPP_HEADER_H#define CPP_HEADER_Hstd::string GetName();Widget GetWidget();#endif  // CPP_HEADER_H
    GOOD
    #ifndef CPP_HEADER_H#define CPP_HEADER_H#include class Widget;std::string GetName();Widget GetWidget();#endif  // CPP_HEADER_H
    [红] 1.2 头文件必须使用宏或预处理指令进行保护,防止嵌套引用
    头文件需要被保护宏包围或在头部加上预处理指令#progma once。
    BAD: header.h
    int Foo();
    GOOD: header.h
    #ifndef CPP_HEADER_H#define CPP_HEADER_Hint Foo();#endif  // CPP_HEADER_H
    GOOD: header.h
    #progma onceint Foo();
    保护宏和预处理指令的选择
    保护宏会在文件重命名或目录结构改动时带来额外的维护工作量,容易产生不同头文件同一宏名的问题
    预处理指令#progma once不属于标准,可能不被部分编译器或较旧的编译器所支持,目前主流编译器均有提供支持
    建议在一般情况优先考虑使用#progma once,在对兼容性有极致需求的情况下才考虑使用宏
    [黄] 1.3 尽量避免使用前置声明
    尽可能地避免使用前置声明。使用#include包含需要的头文件即可。虽然前置声明能够节省编译时间,但会导致以下问题
    前置声明隐藏了依赖关系,头文件改动时,用户的代码会跳过必要的重新编译过程。
    前置声明可能会被库的后续更改所破坏。前置声明函数或模板有时会妨碍头文件开发者变动其 API. 例如扩大形参类型,加个自带默认参数的模板形参等等。
    前置声明来自命名空间 std:: 的 symbol 时,其行为未定义。
    很难判断什么时候该用前置声明,什么时候该用 #include 。极端情况下,用前置声明代替 includes 甚至都会暗暗地改变代码的含义:
    // b.h:struct B {};struct D : B {};// good_user.cc:#include “b.h”void f(B*);void f(void*);void test(D* x) { f(x); }  // calls f(B*)
    如果 #include 被 B 和 D 的前置声明替代, test() 就会调用 f(void*) .
    前置声明了不少来自头文件的 symbol 时,就会比单单一行的 include 冗长。
    仅仅为了能前置声明而重构代码(比如用指针成员代替对象成员)会使代码变得更慢更复杂.
    OK:
    #ifndef CPP_HEADER_H#define CPP_HEADER_Hclass Button;class Label;class Widget;class Windows {public:    Button* GetButton();    // 返回值    void SetLabel(const Label& label);  // 参数private: Widget* _widget;    // 成员变量};#endif  // CPP_HEADER_H
    BETTER:
    #ifndef CPP_HEADER_H#define CPP_HEADER_H#include <button.h>#include <label.h>#include <widget.h>class Windows {public:    Button* GetButton();    void SetLabel(const Label& label);private: Widget* _widget;};#endif  // CPP_HEADER_H
    [黄] 1.4 尽量避免过早使用内联
    内联可以去掉函数调用的成本,同时有可能会使得可执行文件变得臃肿。内联是一种优化,而过早的优化是邪恶的根源(高德纳)。我们可以通过性能分析工具找到那些频繁调用的函数将其内联,但是我们没有办法找到那些不应该内联却已经内联了的函数。如果使用内联,内联函数不要超过2行。
    OK:
    #ifndef CPP_HEADER_H#define CPP_HEADER_H#include #include #include <label.h>class Widgets {public:    std::string GetTitle() {        return title->Text();  // 自动内联 }private: Label* t_itle};inline std::unique_ptr CreateWidgets() {   // 显式声明内联    return std::make_unique();}#endif  // CPP_HEADER_H
    BETTER:
    // widget.h#ifndef CPP_WIDGET_H#define CPP_WIDGET_H#include #include class Label;class Widgets {public:    std::string GetTitle();private: Label* _itle;};std::unique_ptr CreateWidgets();#endif  // CPP_WIDGET_H// widgets.cpp#include <label.h>std::string Widgets::GetTitle() {    return _title->Text();}std::unique_ptr CreateWidgets() {    return std::make_unique();}
    [黄] 1.5 C++中,使用C标准库时,使用不带.h后缀的头文件
    C兼容C语言,所以可以在C中使用C标准库中的头文件,但是C++标准中,旧式的.h后缀的标准库头文件已经废弃,不建议使用,不带.h后缀的头文件会提供更好的优化。
    OK:
    #include <stdint.h>uint16_t GetPort();
    BETTER:
    #include uint16_t GetPort();
    [黄] 1.6 使用标准的头文件引入顺序
    实现文件foo.cpp 的头文件引入顺序,应该如下:
    foo.h (优先位置, 详情如下)
    C 系统文件
    C++ 系统文件
    其他库的 .h 文件
    本项目内 .h 文件
    这种优先的顺序排序保证当 dir/foo.h 遗漏某些必要的库时, dir/foo.cpp或 dir/foo_test.cpp 的构建会立刻中止。因此这一条规则保证维护这些文件的人们首先看到构建中止的消息而不是维护其他包的人们。该规则是确保满足规则1.1的保证
    OK:
    #include “foo.h”#include “bar.h”#include #include <unistd.h>
    BETTER
    #include “foo.h”
    #include <unistd.h>#include 
    #include “bar.h”
    为了避免格式化工具在自动格式化时改动头文件顺序,不同类型的头文件之间可以考虑添加一行空行
    [黄] 1.7 考虑使用纯虚接口类或者PIMPL构建编译防火墙
    C++的类中,既包含了接口(成员函数)又包含了实现(成员变量),我们需要在类定义的头文件中包含成员变量的头文件(如果成员变量不是引用或者指针的话),这样就形成了编译依赖。切除这种依赖的方式是不在接口类中定义实际的成员变量。有两种方式可以完成这件事情:
    OK:
    #include <label.h>class Widget {public:    std::string GetTitle();private: Label _title;};
    BETTER 1: 使用继承——纯虚接口类
    // 接口头文件 widget.hclass IWidget {public:    virtual ~IWidget() = default;    virtual std::string GetTitle() = 0;};// 实现头文件 widgetimpl.hclass Widget : public IWidget {public:    std::string GetTitle();private: Label title;};
    BETTER 2: 使用组合——PIMPL
    // 接口头文件 widget.hclass Widget {public:    Widget(); ~Widget(); // 必须在cpp中实现,否则隐式内联时会因找不到WidgetImpl的析构函数而编译出错    std::string GetTitle();private:    class WidgetImpl; std::unique_ptr pimpl
    ;};// 实现文件 widget.cppclass Widget::WidgetImpl {public: Label _label;};Widget::Widget() : _pimpl(new WidgetImpl) {}Widget::~Widget() = default;
    上面这两种方式对于隐藏实现细节有非常好的效果,后者比前者的开销小一些,因为不需要使用虚函数,而且可以直接在栈上创建对象。前者的接口会相对漂亮一些。
    不要滥用PIMPL,PIMPL会带来一定的性能开销以及代码可读性的降低
    [黄] 1.8 使用不完整类型隐藏 C 语言结构体的实现细节
    在 C 语言中如果想要隐藏结构体的实现细节,可以使用不完整类型(说白了就是指针)。
    OK:
    // point.hstruct Point {    int x;    int y;};
    BETTER:
    // point.htypedef struct Point* point;point CreatePoint(int x, int y);int PointDistance(point_t lhs, point_t rhs);void DeletePoint(point_t point);// point.cstruct Point {    int x;    int y;};
    [红] 1.9 禁止在头文件中使用 using namespace xxx;
    头文件实际上在编译之前插入到引入了它的源文件中,如果我们在头文件中使用了using namespace xxx 就意味着每个引入头文件的源文件中都有using namespace xxx这条指令。这样一来namespace的命名保护功能形同虚设。
    BAD:
    // 头文件#ifndef CPP_WIDGET_H#define CPP_WIDGET_H#include using namespace std;class Widgets {public:    string GetTitle();};#endif  // CPP_WIDGET_H
    GOOD:
    #ifndef CPP_WIDGET_H#define CPP_WIDGET_H#include class Widgets {public:    std::string GetTitle();};#endif  // CPP_WIDGET_H
    补充: C++11 以前,using 分为 using 声明(using std::string)和 using 指令(using namespace std)两种。在头文件中这两种最好都不要使用

  13. [红] 2.1 尽可能少用宏定义
    现代的 C++ 编程中,尽可能的少使用宏,因为它是无类型,无作用域的存在,如非必要,请尽量避免使用。
  14. 作用域
    [黄] 3.1 所有自定义的符号都最好放入到某个 namespace 下面
    命名空间可以很好的防止名字冲突,也可以作为模块划分的一个工具,有可能话,尽量把自定义的符号放入到某个命名 namespace 下面。
    OK:
    int Foo();
    BETTER:
    namespace device{int Foo();}  // namespace device
    例外
    同一个项目中如果之前的代码没有放入到 namespace 中,新的代码也最好不要,因为这样会导致名字不在同一个 namespace 下面,从而导致 ADL 失效,会出现很多编译上的问题。
    [黄] 3.2 推荐使用匿名 namespace 替代 static 声明文件内部可见的符号
    为了防止出现名字冲突,如果一个符号只对文件内部可见,最好把它放入到匿名名字空间下。如果是 C 项目则使用 static 关键字声明。static 关键字声明的符号是内部符号,不会参与到链接过程中,所以不会出现名字问题。
    匿名 namspace 中的符号具有外部链接属性,但是因为它存放在一个匿名的(实际上是编译器生成的一个唯一的名字)名字空间中,所以也不会出现名字冲突问题。此外C++11以前,模板参数需要具有外部链接属性,所以匿名namespace优于static声明。
    OK:
    // a.cppconst int kBufferSize = 10;int Foo() {    return kBufferSize;}// b.cppint Foo() { // 链接的时候产生冲突    return 0;}
    BETTER:
    // a.cppstatic const int kBufferSize = 10;static int Foo() {    return kBufferSize;}// b.cppstatic int Foo() { // 链接的时候不产生冲突    return 0;}
    or BETTER:
    // a.cppnamespace {const int kBufferSize = 10;int Foo() {    return kBufferSize;}}  // namespace// b.cppnamespace {int Foo() { // 链接的时候不产生冲突    return 0;}}  // namespace
    [红] 3.3 禁止在 std 名字空间下定义任何东西
    std是C++的标准名字空间,禁止在这个名字空间下面定义任何的东西,那是未定义行为。你可以特化std中的模板,但不能在里面加任何的东西。
    BAD:
    namespace std {class Widgets {};   // 未定义行为}
    GOOD:
    namespace mynamespace {class Widgets {};}   // namespace mynamespace
    [黄] 3.4 用非成员非友元函数取代成员函数
    一个类内部定义的成员函数越多,这个类的封装性越差,因为成员函数越多,能够访问成员变量的地方越多,改动成员变量可能影响的范围越大。可以考虑把那些不用成为成员函数或友元函数的接口放到和类统一名字空间下面,因为 namespace 的扩展性比类强(namespace内的定义可以分散到不同的头文件中,类的定义却不行)。
    OK:
    class StringUtil {public:    static std::string Join(const std::vectorstd::string& strs, char delimiter);    static std::vectorstd::string Split(const std::string& str, char delimiter);};
    BETTER:
    // a.hnamespace stringutil {std::string Join(const std::vectorstd::string& strs, char delimiter);std::vectorstd::string Split(const std::string& str, char delimiter);};// b.hnamespace stringutil {bool Contains(const std::string& str, const std::string& sub);};
    [黄] 3.5 尽可能缩小局部变量的作用域
    目前主流的编译器通常都不再需要把变量定义在函数的头部,缩小局部变量的作用域,把它放在离它初始化最近的地方声明可以降低未初始化的风险,也可以减少不必要的变量的创建。
    OK:
    int Foo() {    int a = 0;    int b = 0;    // 一些代码,如果这里出现 return,比如前置条件不符合,a,b的创建属于浪费。 a = Bar();    // 一些代码,如果这里使用了 b,b 可能不是一个有效值 b = FooBar();};
    BETTER:
    int Foo() {    // 一些代码    int a = Bar();    // 一些代码    int b = FooBar();}
    [红] 3.6 不要在switch、if…else…、if…else if…else…代码块中,反复声明某个结构体,数组,类等的临时变量,应将其提出到语句块的外层
    BAD:
    struct Contact {    char name[40];    char mobile[40];    char address[256]; …};switch (condition) {    case condition_1: Contact a_contact; // 此处会报错        // operation to a_contact …    break;    case condition_2: Contact b_contact; // 此处会报错        // operation to b_contact … …    break; …    default: …    break;};
    GOOD:
    struct Contact {    char name[40];    char mobile[40];    char address[256]; …};Contact publicContact;switch (condition) {    case condition_1:        // operation to public_contact …    break;    case condition_2:        // operation to public_contact …    break; …    default: …    break;};
    该规则会导致的一个问题是提前声明的变量会保留上一次循环的值,而不是默认值,在某些情况下会导致错误。这其实是违反规则3.5带来的副作用。
    [黄] 3.7 使用 RAII 管理资源的生命周期
    C++编码中,RAII是一个极其重要的概念,它的实质是通过对象的构造和析构去管理资源的生命周期(包括内存资源,文件资源,锁资源等等)。使用 RAII 可以避免程序出现异常的时候导致资源泄露,死锁等问题,也可以避免手动的调用资源的获取和释放。
    OK:
    int Foo() { Widget* widget = new Widget;    // 使用 widget    // 如果这里提前返回了,资源出现泄露    // 如果这里的代码抛出异常,资源出现泄露    delete widget;};int Bar() { mutex_lock.lock();    // 互斥访问某个资源    // 如果这里出现 return,产生死锁    // 如果这里代码抛出异常,产生死锁 mutex_lock.unlock();}
    BETTER:
    int Foo() {    std::unique_ptr widget(new Widget); // widget 的析构会自动删除资源    // 使用 widget    // 提前 return 或者抛出异常,资源同样会释放掉}int Bar() {    std::lock_guardstd::mutex lock(mutex_); // lock 的析构会自动 unlock 资源    // 互斥访问某资源    // 提前 return 或者抛出异常,锁会解除}

  15. [红] 4.1 禁止在基类的构造函数和析构函数中调用虚函数
    虚函数不能在所在类的构造函数中调用。因为在对象的构造函数执行结束之前,对象的虚表指针还没有初始化好,调用虚函数不会有任何的效果。如果你确实需要在对象的构造过程中调用虚函数,可以考虑使用工厂方法模式(Factory Method Pattern)。
    BAD:
    class Widget {public:    Widget() {        Foo(); }    virtual int Foo() {        return 1; }};class Button : public Widget {public:    virtual int Foo() {        return 2; }};
    GOOD:
    class Widget {public:    Widget() {}    virtual int Foo() {        return 1; }};class Button : public Widget {    virtual int Foo() {        return 2; }};std::unique_ptr CreateWidget {    auto widget = std::make_unique(); widget->Foo();    return widget;};
    [红] 4.2 禁止在析构函数中抛异常
    析构函数中抛出异常可能会导致程序直接崩溃退出,如果你的析构函数可能会抛出异常的话(比如你调用的某个函数可能会抛出异常),请使用 try catch 捕获它。
    BAD:
    class Widget {public: ~Widget() {        Deinit();   // 这里会崩溃 }    void Deinit() {        throw std::runtime_error(“descturct error”); }}
    GOOD:
    class Widget {public: ~Widget() {        try {            Deinit(); } catch(const std::runtime_error& error) {            // 处理异常 } }    void Deinit() {        throw std::runtime_error(“descturct error”); }}
    [黄] 4.3 尽量避免隐式转换
    隐式转换很可能会带来你意想不到的BUG,因为转换由编译器自动完成,查找起来特别困难。隐式转换分为两种,单参构造函数(有默认参数的多参构造函数也算)和类型转换操作符。
    OK:
    class Widget {public:    Widget(const std::string& name);    // 使得 Widget w = std::string(“widget”); 合法    operator std::string ();    // 使用 std::string s = w; 合法};
    BETTER:
    class Widget {public:    explicit Widget(const std::string& name); // Widget w(“widget”);    std::string ToString();    // 这只在 C++11 中合法,在 C++11 之前不能使用    explicit operator std::string ();    // std::string s = static_caststd::string(w);};
    [黄] 4.4 考虑编写或者禁用拷贝构造
    拷贝构造对于资源管理来说是一个难题,如果你的类是一个资源管理类,考虑手动编写拷贝构造或者选择禁用拷贝构造。
    OK:
    class Widget {public:    Widget() : _size(1024), buffer(new char[size])    // 不写拷贝构造 ~Widget() {        delete [] _buffer; }private:    size_t _size;    char* _buffer;};Widget widget;Widget copy(widget);    // 析构的时候出现崩溃
    BETTER:
    class Widget {public:    Widget() : _size(1024), _buffer(new char[_size])    // C++11 版本    Widget(const Widget& widget) = delete; Widget& operator=(const Widget& widget) = delete; ~Widget() {        delete [] _buffer; }private:    size_t _size;    char* _buffer;};Widget widget;// Widget copy(widget); 编译无法通过
    or
    class Widget {    Widget() : _size(1024), _buffer(new char[_size]) ~Widget() {        delete [] _buffer; }private:    // 定义但是不实现    Widget(const Widget& widget); Widget& operator=(const Widget& widget);private:    size_t _size;    char* _buffer;};Widget widget;// Widget copy(widget); 编译无法通过
    [黄] 4.5 拷贝构造,赋值操作符,析构函数最好同时定义
    这三个函数如果定义了其中一个,通常意味着另外两个也需要同时定义。
    OK:
    class Widget {    Widget() : _size(1024), _buffer(new char[_size]) ~Widget() {        delete [] _buffer; }private:    size_t _size;    char* _buffer;};
    BETTER:
    class Widget {    Widget() : size(1024), buffer(new char[size]) ~Widget() {        delete [] buffer; }    Widget(const Widget& widget) : size(widget.size), buffer(new char[size]) { std::copy(widget.buffer widget.buffer + widget.size, buffer); } Widget& operator=(const Widget& widget) { Widget copy(widget); std::swap(*this, copy);        return *this; }private:    size_t size;    char* buffer;};
    [黄] 4.6 不要在 struct 中定义成员变量以外的东西
    在C++中,struct和class基本上没有区别(除了默认的访问权限外),通常struct只用于定义数据的集合,如果你需要定义成员函数,请使用class关键字。
    OK:
    struct Address {    uint16_t port;    const std::string ip;    Address() = default;    explicit Address(uint16_t port);    explicit Address(const std::string& ip);    Address(const std::string& ip, uint16_t port);};
    BETTER:
    struct Address {    uint16_t port;    const std::string ip;};Address CreateAddress(uint16_t port);Address CreateAddress(const std::string& ip);Address CreateAddress(const std::string& ip, uint16_t port);
    [黄] 4.7 能用组合的地方,尽量少用继承
    继承在C++中是仅次于友元的耦合关系,基类中的任何接口的改动都会影响到所有的子类。避免使用多重继承。
    OK:
    class Widget {public:    void Action() {        // 初始化        DoAction(); };private:    virtual void DoAction() = 0;};class Button : public Widget {    virtual void DoAction() {}}
    BETTER:
    class Widget {public:    using ActionType = std::function<void()>;    // 依赖注入    Widget(ActionType action) : _doAction(action) {}    void Action() {        // 初始化        _doAction(); }private: ActionType _doAction;};
    这里有一个更全面的案例。
    [红] 4.8 所有接口类的成员变量应定义为 private
    对于接口类(会对外暴露的类)来说,它的实现细节不应该暴露给接口的使用者。因为只要你暴露出去,你将无法确定修改这个数据成员会造成多大的影响(你无法确定有多少人使用了你的接口)。
    protected 成员和 public 成员在暴露实现细节方面没有本质的区别,因为你没有办法确定将会有多少人继承自你的类,也就同样无法确定修改它会造成什么样的问题。
    接口类最好使用 PIMPL 或者纯接口类,不要包含实际的数据成员。如果你的数据成员确实属于接口的一部分,请考虑重构设计(比如使用 struct 存放数据)
    BAD:
    class Widget {public: std::string _name;protected:    int _width;    int _height;};
    GOOD:
    class Widget {public:    std::string GetName();private: std::string _name;    int _width;    int _height;};
    or BETTER:
    // 头文件中class Widget {private:    class WidgetImpl; std::unique_ptr _pimpl;};// cpp 文件中class Widget::WidgetImpl {public: std::string _name;    int _width;    int _height;};
    例外
    static const数据成员可以是public
    [红] 4.9 使用统一的成员声明顺序
    类的成员声明顺序应该如下:
    public
    protected
    private
    其中每一种权限可以有多个区块:

public

public

private

private

每个区块的成员顺序应该如下:
类型 (包括 typedef, using 和嵌套的结构体与类)
常量
工厂函数
构造函数
赋值运算符
析构函数
其它函数
数据成员
[黄] 4.10 尽量不要重载操作符
操作符的重载有时候可以提升代码的表达力,比如 std::string 的 operator+ 运算符。但是对于用户自定义类型,运算符的含义比较难定义,用的不好对于代码的表达能力的提升效果可能会适得其反。
如果操作符的含义有歧义,不要重载操作符
比如,用 + 表示两个点的距离可能会有歧义。double distance = a + b 没有double distance = DistanceOf(a, b) 的可读性强
BAD:
struct Point {    int x;    int y;};double operator+(const Point& lhs, const Point& rhs);
BETTER:
struct Point {    int x;    int y;};// 用命名函数可能会好一些double DistanceOf(const Point& lhs, const Point& rhs);
如果操作符的含义没有歧义,且能提升代码的可读性,可以重载操作符
比如,用表达两个点是否是同一个点不会有太大的歧义,if (a == b) 可能会比if (EqualsTo(a, b)) 可读性强一些。
OK:
struct Point {    int x;    int y;};bool EqualsTo(const Point& lhs, const Point& rhs);
BETTER:
struct Point {    int x;    int y;};// 用操作符看上起自然一些bool operator
(const Point& lhs, const Point& rhs);
[红] 4.11 基类的析构函数必须是 public virtual 或 protected non virtual
继承包含了重用和接口两重属性,如果你想要使用虚函数机制,也就是说你使用继承的接口属性,那么基类的析构函数应该是public virtual,否则很可能会导致内存泄露。如果不使用虚函数机制,也就是说你使用继承的重用属性,那么基类的析构函数应该是protected non virtual,这样可以避免很多错误。
使用虚函数
class IWidget {public:    virtual int Foo() = 0;};class Widget : public IWidget {public:    Widget() :  _val(new int) {} ~Widget() { delete _val; }    virtual int Foo() {        return 0; }private:    int* _val;};int main() { IWidget* widget = new Widget;    delete widget;  // 内存泄露,因为 IWidget 的析构不是虚函数,子类 Widget 的                    // 析构函数不会被调用,子类中的资源不会被释放。}
补充:千万不要 public 继承自一个没有虚析构函数的类,考虑使用组合
不使用虚函数
下面是禁用拷贝构造的一个基类,继承自它可以禁用拷贝构造
// 这个类的存在是为了重用功能,我们不希望有它的对象单独存在class NonCopyable {protected:    NonCopyable() = default; ~NonCopyable() = default;private:    NonCopyable(const NonCopyable&); NonCopyable& operator=(const NonCopyable&);};class Widget : private NonCopyable { };int main() {    // NonCopyable noncopyable; 这个地方无法通过编译,因为它的析构是不可访问的 Widget w;    // Widget copy(w); 这里无法通过编译,因为拷贝被禁用了}
补充:如果你的基类需要有单独的对象存在,那么你应该考虑组合而不是继承
5. 函数
[黄] 5.1 优先使用 const 引用传递参数
在C++中有两种参数传递方式,值传递和引用传递,对于用户自定义类型,值传递会有拷贝构造的调用以及临时对象的生成,应该尽可能的避免。
OK:
int GetValueByType(std::string name);
BETTER:
int GetValueByType(const std::string& name)
例外
C++11 中存在除了拷贝语义之外还有移动语义,如果你的函数需要完全托管资源的所有权可以考虑使用传右值和std::move()
void Widget::SetName(std::string &&name) { _name = std::move(name);    // 托管资源}
[黄] 5.2 尽量减少输出参数
输出参数容易导致不太自然的代码,尽量避免。使用输出参数通常有下面几个原因:

效率,减少拷贝

C++目前的编译器基本都支持 RVO(Return Value Optimization) 和 NRVO(Named Return Value Optimization),直接返回一个对象(即使是一个比较大的对象,比如 std::string, std::vector)通常都不会造成额外的开销。如果你确定你使用的编译器支持 RVO,可以不用为了减少拷贝使用输出参数。

多个返回值

考虑使用 std::pair 和 std::tuple

错误处理

考虑使用异常机制替代返回值的方式处理错误

OK:
int a;Foo(&a);
BETTER
int a = Foo();
[黄] 5.3 输入参数放在输出参数前面
参数最好区分输入参数和输出参数,输入参数放在输出参数前面。通常来说,输出最好使用返回值进行传递。如果既有输入参数,也有输出参数,需要将输入参数放在前面,输出参数放在后面。因为从逻辑上来说,一个函数应该是先有输入后有输出,这样做有利于提高函数可读性。
例外:
C++不允许有默认值的参数放到没有默认值的参数前面,如果你的输入参数有默认值,它只能放到参数列表的后面。
OK:
bool GetValueByType(int& val, const std::string& type);
BETTER:
bool GetValueByType(const std::string& type, int& val);
[黄] 5.4 函数最好不要超过 40 行
过长的函数通常都是一个函数做了太多的事情,这会导致代码的耦合性非常大,BUG定位很困难。一个函数推荐在 20 行以内,最好不要超过 40 行。超过 40 行的代码很难在一屏中显示完全(除非你的字体很小),需要翻页才能读完的函数会比较难以理解。
越是简短的函数越容易理解,BUG也越少。
OK:
// 打开几个项目文件
BETTER:
// 重构
[红] 5.5 统一输出参数的传递方式
输出参数可以使用引用传递,也可以使用指针传递。引用和指针各有千秋,指针比引用灵活但是容易出错。请在你的代码模块内,使用同一种输出参数的传递方式。
BAD:
void Foo(int& a);void Bar(int* b);
GOOD:
void Foo(int& a);void Bar(int& b);
or
void Foo(int* a);void Bar(int* b);
[黄] 5.6 使用函数重载时,确保它们的逻辑含义是一样的
有时候函数重载可以让接口更加统一,但是如果两个接口本身的逻辑含义并不相同,使用函数重载会导致接口的混乱。
OK:
// 判断包是否合法bool IsLegalPacket(const Packet& packet);// 判断数据流中是否包含合法的包bool IsLegalPacket(const std::vector<uint8_t>& bitstream);
BETTER:
// 判断包是否合法bool IsLegalPacket(const Packet& packet);// 判断数据流中是否包含合法的包bool ContainsLegalPacket(const std::vector<uint8_t>& bitstream);
[黄] 5.7 考虑使用提前返回的方式减少嵌套
嵌套过深的代码会使得逻辑极其混乱,难以理解。可以通过提前返回的方式把嵌套结构变成顺序结构,通常建议嵌套不要操过两层。
OK:
if (userResult == SUCCESS) {    if (pemissionResult != SUCCESS) { reply.WriteErrors(“error reading permissions”); reply.Done();        return; } reply.WriteErrors(“”);} else { reply.WriteErrors(userResult);}reply.Done();
BETTER:
if (userResult != SUCCESS) { reply.WriteErrors(userResult); reply.Done();    return;}if (pemissionResult != SUCCESS) { reply.WriteErrors(“error reading permissions”); reply.Done();    return;}reply.WriteErrors(“”);reply.Done();
补充:在循环中可以考虑使用 continue 来提前结束本次循环,从而避免产生嵌套
6. 命名
最重要的一致性规则是命名管理. 命名的风格能让我们在不需要去查找类型声明的条件下快速地了解某个名字代表的含义: 类型, 变量, 函数, 常量, 宏, 等等, 甚至. 我们大脑中的模式匹配引擎非常依赖这些命名规则。
命名规则具有一定随意性, 但相比按个人喜好命名, 一致性更重要, 所以无论你认为它们是否重要, 规则总归是规则。
名字应该能够让使用者一眼看出这是个变量、函数、常量或者是一个宏,如果用户不需要查看定义就能确定这些点,则这就是个好的命名。
[黄] 6.1 通用命名规则
函数命名、变量命名、文件命名要有描述性,少用缩写。
尽可能使用描述性的命名, 别心疼空间, 毕竟相比之下让代码易于新读者理解更重要。 不要用只有项目开发者能理解的缩写, 也不要通过砍掉几个字母来缩写单词。
BAD:
int n;                     // 毫无意义的int nerr;                  // 一个模棱两可的缩写int n_comp_conns;          // 同上.int wgc_connections;       // 这种只有熟悉这些逻辑的人才能看懂‘wgc’的意义int pc_reader;             // 太多的东西可以用 “pc"来表示了.int cstmr_id;              // 删掉了单词内的字母以为就能得到一个好的缩写?
GOOD:
int priceCountReader;    // 完整的描述int numErrors;            // “num” 是个使用比较广的缩写.int numDnsConnections;   // 大多数人都知道 “DNS” 是什么意思.
[红] 6.2 文件命名
文件命名首先遵守目前代码中的文件命名方式以及扩展名后缀形式。
如果目前代码中文件名命名混乱,则文件名跟类名保持一致。
比如:
MyUsefulClass.cpp
C++的文件名应该以.cpp为后缀,头文件应该以.h为后缀。如果你的头文件是模板实现文件可以使用.hpp为后缀。
不要使用系统中存在的头文件名称,比如 /use/include 下面的名称。
通常应尽量让文件名更加明确. HttpServerLogs.h 就比 Logs.h 要好。
定义类时文件名一般成对出现, 如 FooBar.h 和 FooBar.cpp, 对应于类 FooBar.
BAD:
HttpLogs.cHttpServerLogs.h
GOOD
HttpServerLogs.cHttpServerLogs.h
[黄] 6.3 类型命名
类型名称的每个单词首字母均大写, 不包含下划线: MyExcitingClass,MyExcitingEnum。
这条规则适用于所有的类名(class), 结构体名(struct), 别名(type aliases), 枚举(enums), 和类型模板参数(type template parameters)。
OK:
// classes and structsclass UrlTable { …}class UrlTableTester { …}struct UrlTableProperties { …}// typedefstypedef hash_map<UrlTableProperties , string> PropertiesMap;// using aliasesusing PropertiesMap = hash_map<UrlTableProperties , string>;// enumsenum UrlTableErrors { …}
[黄] 6.4 变量命名
变量 (包括函数参数) 和数据成员名一律小写开头,小驼峰式,类成员变量使用下划线开头,但结构体的就不用, 如: tableName, _tableName。
普通变量命名
BAD:
string table_name; 
OK:
std::string tableName;std::string _tableName;
类成员变量命名
不管是静态的还是非静态的, 类数据成员都可以和普通变量一样, 但要接下划线。
BAD:
class TableInfo {private: string tableName;     // 不以下划线开头 string table_name;    // 不是驼峰式 string _table_name    // 不是驼峰式};
OK:
class TableInfo { …private: string _tableName; static Pool
pool;};
结构体成员变量名
不管是静态的还是非静态的, 结构体数据成员都可以和普通变量一样, 不用像类那样接下划线接口
BAD:
struct UrlTableProperties { string name
;  int num_entries_;  static Pool
pool_;};
OK:
struct UrlTableProperties { string name;  int numEntries;  static Pool* pool;};
[黄] 6.5 常量命名
声明为 constexpr 或 const 的变量, 或在程序运行期间其值始终保持不变的, 命名时以k开头, 大小写混合。
const int kDaysInAWeek = 7;
[黄] 6.6 全局变量命名
声明全局变量, 命名时以g开头, 大小写混合。
int gCount = 1;
我们尽量不要使用全局变量,会带来耦合和数据竞争的风险,如果必须要用,考虑使用单例模式代替
[黄] 6.7 函数命名
普通函数以大写字母开头,并且每个单词的首字母都要大写(如CamelCase,PascalCase),类似这些的函数不要使用下划线。如果是单词的缩写的话,最好也是首字母大写,而不是所有都大写(StartRpc(), 不是写成StartRPC())
OK:
AddTableEntry();DeleteUrl();OpenFileOrDie();
[黄] 6.8 命名空间命名
命名空间的名称都应该都用小写字母顶层命名空间应该使用工程名或将代码包含进这个命名空间的团队的名称,命名空间的右括号后面加上注释表示匹配的对象。
BAD:
namespace MST {}
OK:
namespace xuanwen{namespace tv {}  // namespace tv}  // namespace xuanwen
[红] 6.9 不要在命名空间中嵌套一些已经有的知名的命名空间
特别注意不要在命名空间中嵌套std 命名空间。
BAD:
namespace xuanwen{namespace std {}  // namespace std}  // namespace xuanwen
BETTER:
namespace xuanwen{namespace tv {}  // namespace tv}  // namespace xuanwen
[红] 6.10 枚举命名和常量一样命名
BAD:
enum UrlTableErrors { ok = 0, out_of_memory, unsupport_format,};
GOOD:
enum UrlTableErrors { kOk = 0, kOutOfMemory, kUnsupportFormat,};
[红] 6.11 如果使用位域,请不要在变量命中包含变量类型,而应该说明用途
BAD:
U8 u8_tv_source:1;
虽用U8定义,但位域只分配1个bit,变量前加u8,容易误解变量的取值范围。实际分配1个bit,通常用来做开关判断,应该用变量名说明其用途。
GOOD:
U8 tvSourceEnable:1;
7. 注释
注释也是代码的一部分,一旦写下就需要维护。此外注释的维护成本很高,因为注释用来解释代码,这意味着你写下注释的那一刻你的代码就产生了冗余,而冗余给维护带来较大的困难。你很可能会在修改一段代码的时候,忘记去修改一段注释,导致你的注释和你的代码相左,最终让一段解释性的描述变成了一段误导性的描述。
我们建议:
尽量写一段通俗易懂的代码,而不是一段晦涩难懂的代码加上一段通俗易懂的注释
尽量不要写一些显而易见的注释,比如重复函数命名和参数的命名
对接口的作用写注释,而不是对接口的实现写注释,因为接口通常是稳定的,而接口的实现不一定稳定
对关键的算法写注释,而不是对算法的实现写注释
[黄] 7.1 注释风格
可以使用//或者/* /,我们建议使用//。
请坚持使用一种注释风格。
[红] 7.2 类注释
每一个类都应该注明其作用,和如何使用这个类。
BAD:
class GargantuanTableIterator { …};
GOOD:
// 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 { …};
[黄] 7.3 函数注释
在声明时加注释来描述函数的使用,在定义函数时加注释来描述函数如何运作。
在函数声明的注释中应该提到的点有:
输入和输出分别是什么
如果是函数成员函数:在函数调用完成后,对象是否会保存引用参数,是否会将引用参数的资源释放
函数是否有申请内存而且需要调用者进行释放
函数的参数中是否有哪些参数允许为空指针(NULL)
函数调用时是否有哪些性能隐患
函数是否是可重入(re-entrant)的,假设在线程同步时他如何表现的。
OK:
// 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;
注意: 如果函数有重载存在,则应该将注释重点放在重载上,而不是重复之前的注释,大多数情况下都是相同的,所以可以不做注释。
[黄] 7.4 实现注释
如果函数的实现中有哪些特别难的地方,则应该有解释性的注释。不要用自然语言去解释一段代码在做什么而是说明为什么要这么做。如果你的代码需要靠注释才能够让人明白它在做什么那么你可能需要重构这一段代码。
例如,在定义时注明代码中有哪些代码窍门,还有整个函数的构造预览,或者解释下为什么要这样实现函数,而不是使用其他的方法。例如你可能需要解释为什么要在前半部分加锁,而不是在后半部分。
不要做一些很明显的,不必要的冗长的注释。
避免冗余的注释
BAD:
// Returns true if the table cannot hold any more entries.bool IsTableFull();
GOOD:
bool IsTableFull();
你为什么这样做而不是做了什么
BAD:
// 在容器中查找元素 <<== 傻子都看的出来auto iter = std::find(v.begin(), v.end(), element);if (iter != v.end()) {  Process(element);}
GOOD:
// 只有在之前没有处理过这个元素的时候才处理这个元素auto iter = std::find(v.begin(), v.end(), element);if (iter != v.end()) {  Process(element);}
同样,在构造和析构函数中,大多数人是知道他们的功能的,所以不需要写上类似于”destroy this object”,只需要写明构造时如何处理他的参数,析构时如何清理对象,如果不是很重要的话就什么都不要写。
[黄] 7.5 变量注释
变量名称是变量最好的注释,起一个好的名字比起一个烂的名字加上一段好的注释要好很多。除非有一些信息你无法使用类型和变量来清晰的说明你才需要加上注释去说明一个变量(无论是普通变量还是成员变量都是如此)。
BAD:
private:    // entity 的数量    int _numTotalEntries;
GOOD:
private:    // -1 表示还不知道有多少个 entity    int _numTotalEntries;
[红] 例外
所有的全局变量都应该写注释,写明他们是做什么用的,如果有必要的话,还应该说明为什么需要将其定义为全局变量。
BAD:
const int kNumTestCases = 6;
GOOD:
// 在这次回归测试中需要运行的测试用例的数量const int kNumTestCases = 6;
[黄] 7.6 实参注释
如果函数调用的实参没有实际的含义,可以给他们加上适当的注释,或者直接重构代码。
OK:
const DecimalNumber product =    CalculateProduct(values, 7, false, nullptr);
GOOD:
const DecimalNumber product =    CalculateProduct(values,                    /precisionDecimals=/7,                    /useCache=/false,                    /completionCallback=/nullptr);
or BETTER:
ProductOptions options;options.SetPrecisionDecimals(7);options.SetUseCache(ProductOptions::kDontUseCache);const DecimalNumber product =    CalculateProduct(values, options, /completionCallback=/nullptr);
[黄] 7.7 形参注释
如果函数的形式参数含义不是特别清晰,可以加注释,但更推荐用以下方式解决:

如果函数的返回值为bool类型,则最好将其定义成枚举(enum),这样会使参数的自描述性更强

如果函数有多个参数,需要考虑是否将这些参数其独立成一个类或者结构体,然后将这个类或结构体对象传递给参数,这样做的好处是减少函数的参数数量,而且因为参数有了名称,也更容易被读懂。另一方面,当你这个函数需要更多或更少的参数的时候,你不需要去改所有调用到函数的地方,只需要修改结构体和函数实现就可以了。

将又大又复杂的嵌套表达式改为用变量。

[黄] 7.8 版权注释
由我们添加的模块源文件需要添加版权信息。
版权信息包含三部分:
符号©(字母C外加一个圈),或者单词“Copyright”版权,或者缩写“Copr.”
作品初次公开发表的年份
作品的版权拥有者名称,或者可识别的拥有者名称缩写,或者是拥有者的其他为公众知晓的名称
文件的编码方式
样例:
/* Copyright (©) 2019 Guangzhou XuanWen Co.,Ltd. * All rights reserved. * Author: xxx xxx@xuanwen.com * * -- coding: uft-8 -- /
8. 格式
格式的首要原则是统一
格式的首要原则是统一
格式的首要原则是统一
请遵循项目中现有代码的格式
9. 其他
这一章节的要求均为[黄]线内容,它能提高你的设计水平,同时提高代码质量,请尽量使用它。
[黄] 9.1 尽量使用智能指针替代裸指针
指针非常灵活也非常危险,很容易造成内存泄露,程序崩溃等问题。智能指针可以帮助我们管理指针的声明周期,避免出现空指针,野指针,重复删除等等问题。C++11中包含两种智能指针:
std::unique_ptr 独占式
std::shared_ptr 共享式,基于引用计数
C++98 中有另一个独占式的智能指针std::auto_ptr但是已经废弃。
如果你的编译器不支持C11,可以考虑切换编译器,C17都要出来了,11已经发布6年了,绝大多数编译器应该都支持C11。如果你的实在找不到支持C11的编译器可以考虑使用boost::shared_ptr,这个实现是C11标准的基础,支持在C98编译器中运行。
OK:
int Foo(int size) {    char
buffer = new char[size];    // 处理 buffer,如果出现异常或提前返回出现内存泄露    delete buffer;}Widget* Bar() {     // 如果用户忘记删除,出现内存泄露,如果用户多次删除出现崩溃    return new Widget;}
BETTER:
int Foo(int size) {    std::unique_ptr<char[]> buffer(new char[size]);    // 处理 buffer}std::unique_ptr Bar() { // 用户不再需要考虑删除问题    return std::unique_ptr(new Widget);}
上面只是举例,在C++中你通常都不会需要使用动态分配的数组,因为你可以直接使用容器
[黄] 9.2 避免使用 RTTI 和 dynamic_cast
需要用到 dynamic_cast 的情况基本上都是设计上存在问题,此时应该考虑类的设计是否合理;dynamic_cast 需要逐个查找虚表,在嵌套层次很深的情况下,这个操作开销特别大。
OK:
void Foo(Base* base) {    if (dynamic_cast<A*>(base) != nullptr) {        // 处理 A } else if (dynamic_cast<B*>(base) != nullptr) {        // 处理 B } else if (dynamic_cast<C*>(base) != nullptr) {        // 处理 C }}void Bar(Base* base) {    if (typeid(data) == typeid(D1)) {        // } else if (typeid(data) == typeid(D2)) {        // } else if (typeid(data) == typeid(D3)) {        // }
BETTER:
void Foo(Base
base) { base->SomeVitrualFunction();}void Bar(Base
base) { base->SomeVitrualFunction();}
补充:解决上面这样的问题除了用虚函数,还有一种选择是使用访问者模式完成双分派
[黄] 9.3 C++中,避免使用C式类型强转
强转是在给类型系统开后门,静态类型语言的类型系统是安全性很重要的一个保证。尽量避免强转,即使不得不使用也不要使用 C 式的强转,因为这种强转不利于查找,强转类型无法区分。有时是在做强制转换 (如 (int)3.5), 有时是在做类型转换 (如 (int)“hello”).
OK:
long a;int b = (int) a;char arr[] = “hello world”;const char
kStr = arr;char* str = (char*) hello;char* buffer = (char*) malloc(1024);
BETTER:
long a;int b = static_cast(a);char arr[] = “hello world”;const char* kStr = arr;char* str = const_cast<char*>(hello);char* buffer = reinterpret_cast<char*>(malloc(1024));
[黄] 9.4 C中,优先使用i而不是i++
C允许重载操作,所以i和i最终可能是调用一个函数,i需要返回之前的值然后在递增,这种情况可能会导致不必要的临时变量的产生,比如这两个操作符的通常实现如下:
Widget& operator++() { ++_count;    return *this;}Widget operator++(int) {    // 后置形式    Widget orig(*this);     // 这里要保存一份拷贝 ++(*this);    return orig;}
OK:
for (auto iter = iv.begin(); iter != iv.end(); iter++) {    // 处理当前元素}
BETTER:
for (auto iter = iv.begin(); iter != iv.end(); ++iter) {    // 处理当前元素}
[黄] 9.5 定义常量时考虑使用const替代宏
宏定义是一种无视任何作用域,无视任何类型的存在。在C++中请考虑使用常量替代宏定义。
OK:
#define BUFFER_SIZE (1024)char buffer[BUFFER_SIZE];
BETTER:
const size_t kBufferSize = 1024;char buffer[kBufferSize];
[黄] 9.6 C++中,慎用宏定义
宏定义的过度使用不利于代码的维护。如果需要使用宏定义请考虑下面这些建议:

尽量不要在头文件中定义宏

如果你想要定义一些常量,可以使用 extern const 替代

OK:

// 头文件#define MAX_SIZE (2048)

BETTER:

// 头文件extern const size_t kMaxSize;// cppconst size_t kMaxSize = 1024;

[黄] 9.7 优先使用 nullptr 替代 NULL,0
C++11 引入了新的类型 nullptr_t 和值 nullptr,nullptr_t 可以转换成任何的指针类型,NULL 是一个宏定义,在不同的编译器中定义可能各不相同,可移植性没有 nullptr 强。不要使用 0 表示空指针;NULL可能导致使用错误的函数重载,例如
void Foo(int i); // 1void Foo(int i); // 2Foo(NULL) // NULL可能是0的宏定义,因此会错误地调用1Foo(nullptr) // 正确地调用了2
OK:
void buffer = NULL;
BETTER:
void
buffer = nullptr;
[黄] 9.8 优先使用sizeof(val)而不是sizeof(type)
使用sizeof(val)可以应对 val 类型更改问题,sizeof(type)则不行。但是使用sizeof(val)需要特别注意指针问题。
OK:
int a;size_t size = sizeof(int);
BETTER:
int a;size_t size = sizeof(a);
[黄] 9.9 C++中,优先使用 using 替代 typedef 定义别名
using定义别名支持模板参数,typedef则不支持,在C++11中推荐使用using替代typedef定义别名。
OK:
typedef std::mapstd::string, int Map;Map mymap;
BETTER
template using Map = std::map<std::string, V>;
[黄] 9.10 优先使用标准库中的算法替代手写算法
STL 中除了容器,还有非常重要的一部分是算法,这些算法的效率远比我们手写代码的效率高,推荐优先使用这些算法。
std::copy
OK:
int Foo(int
buffer, size_t size) { std::vector ia;    for (size_t i = 0; i != size; ++i) { ia.push_back(buffer[i]); }}
BETTER:
int Foo(int* buffer, size_t size) { std::vector ia; std::copy(buffer, buffer + size, std::back_inserter(ia))}
这里只是例子,更好的做法是在构造 ia 的时候初始化它,std::vector(buffer, buffer + size)
std::fill
OK:
int Foo() {    const size_t kBufferSize = 1024;    int buffer[kBufferSize];    for (size_t i = 0; i != kBufferSize; ++i) { buffer[i] = 1147; }}
BETTER:
int Foo() {    const size_t kBufferSize = 1024;    int buffer[kBufferSize]; std::fill(buffer, buffer + kBufferSize, 1147);}
std::find
OK:
int Foo(const std::vector& ia) {    for (auto iter = ia.begin(); iter != ia.end(); ++iter) {        if (ia == 1147) {            // finded } }}
BETTER:
int Foo(const std::vector& ia) {    auto iter = std::find(ia.begin(), ia.end(), 1147);    if (iter != ia.end()) {        // finded }}
这样的例子数不胜数,推荐大家大致翻一下标准库中包含哪些可用的算法,以备不时之需
[黄] 9.11 避免在代码中出现魔数或硬编码
通常在代码中不推荐使用数值字面量,这样的字面量通常被成为魔数,对于代码的可读性和可维护性都非常不利。如果一定要有字面量,请给给这个字面量加上注释,或者用一个有意义的名字替代它。
BAD:
nextItem = currentItem + 3;   // 谁能告诉我这个 3 是什么意思
OK:
nextParentItem = currentParentItem + 3; // 每两个父节点之间有三个子节点
or BETTER:
const int kChildNumberBetweenParent = 3;nextParentItem = currentParentItem + kChildNumberBetweenParent;
[黄] 9.12 避免在代码中执行命令
在代码中执行命令会存在以下问题
命令通常是平台相关的,会带来跨平台问题
命令在不同版本的参数,执行效果和输出都可能有变化
调用第三方命令的效率较差
命令可能不存在,即使是最常见的命令,在某些精简的系统上也可能会不存在
因此,不建议在代码中执行命令行
BAD:
char cmdBuf[128]={0};snprintf(cmdBuf, 128, "sed -i 's/CountryRegion=.
/CountryRegion=5/g’ %s" , /dev/shm/MT76x2STA.dat);SystemCmd(cmdBuf);SystemCmd(“sync”);
OK:
FILE *fp1 = NULL;fp1 = fopen(/dev/shm/MT76x2STA.dat, “r”);if (fp1 != NULL){    char buffer[128] = {0};    while(fgets(buffer, sizeof(buffer), fp1) != NULL) {        if (strncmp(buffer, “CountryRegion=”, strlen(“CountryRegion=”)) == 0) {            fprintf(fp1, “CountryRegion=5\n”); }                      memset(buffer, 0, sizeof(buffer)); }    fclose(fp1);}
[黄] 9.13 避免交换条件表达式的参数
部分传统的书籍或博客中可能会推荐通过交换相等判断表达式两端的参数避免将误写成=带来的问题(《编写可读代码的艺术》一书中称之为“尤达表示法”),然而该方式违背了自然语言的理解顺序,会降低代码的可读性,且现代编译器在将误写成=都会发出警告,问题很容易被发现(前提是我们不要轻易忽略任何一个警告)
BAD:
if (0 == i) {    // other code}
OK:
if (i == 0) {    // other code}
[黄] 9.14 不要轻易忽略任何一个警告
现代编译器会针对行为可疑的代码提出抱怨,虽然并不会导致编译出错,但出现警告的代码往往是有一定问题的,编译器的作者比你更清楚那些代码在干什么。在你准备忽略警告前,请拿出充分的理由。
10. 宝箱
这一章记录的都是前人掉过的坑,排过的雷,我们希望你不要再“牺牲”在这些地方。
[红] 10.1 流程控制语句间一定要用{}包含处理语句块,哪怕只有一行语句
BAD:
if (flag) statement1;else statement2;
错误代码说明:如果不用{}包含语句块,当需要在else中添加 statement_3 的时候,极易遗忘{},引起逻辑问题。
GOOD:
if (flag){ statement1;}else{ statement2;}
[红] 10.2 复杂表达式,要用()明确表示优先级
没有人,也没有必要时时刻刻都记得不同操作符之间的优先级。人的阅读与思维习惯总是由左向右的,如果你的表达式中含有破坏这种习惯的表达式,请使用 ()。
BAD:
if (vaule1 >> 8 & value2 >> 4){ …}
错误代码说明:需要清楚记得&(按位与)优先级低于>>(右移)。且阅读时,容易理解为“vaule1 >> 8和vaule2进行&操作后,再>>4”。
GOOD:
if ((vaule1 >> 8) & (value2 >> 4)){ …}
[红] 10.3 禁止使用不相关的表达式或变量做if else的流程选择
BAD:
if (height < 150) // 身高小于150cm{ …}else if (height < 180) // 身高小于180cm{ …}else if (weight < 50)  // 体重小于50kg–错误的跟写 else if{ …}
错误代码说明:身高、体重在此为不相关的数据,在后面跟的else if添加体重的判断不正确,应另新的if条件判断。而如上能进入此条件的仅为体重小于50kg但身高却大于180cm显然是不合理的。
GOOD:
if (height < 150) // 身高小于150cm{ …}else if (height < 180) // 身高小于180cm{ …}if (weight < 50)  // 体重小于50kg{ …}…
[黄] 10.4 避免滥用extern来声明函数或变量,在函数内部使用extern或在同层使用extern
OK:
int Bar(){    extern void Foo();    Foo(); …}
BETTER:
extern void Foo() //统一放到前面…int Bar(){    Foo(); …}
OK:
int Bar(){    extern void Foo();    Foo(); …}
BETTER:
include_file.hextern void Foo();…bar.c#include “include_file.h” //通过头文件来引用…int bar(){    Foo(); …}
[红] 10.5 不要在代码中保留“赤裸裸的”printf,cout等打印语句
通常情况,打印信息是用来debug的,即只有当调试时才真正有用。如果真的需要保留,请把打印语句用“调试宏”包含起来,并默认将“调试宏”设置为关闭状态。这样既可以方便你在下次调试时继续使用,也不会因为输出信息太多,影响他人的调试效率。最好的措施是使用被广泛使用的日志库
BAD:
int Foo(){    int returnValue = Bar();    printf(“returnValue = %d\n”, returnValue); …}
GOOD:
#define FUNC_DBG(x) //xint Foo(){    int returnValue = Bar();    FUNC_DBG(printf(“returnValue = %d\n”, returnValue)); …}
or
#define FUNC_DBG_PRINT(x) //printf(x)int Foo(){    int returnValue = Bar();    FUNC_DBG_PRINT(“returnValue = %d\n”, returnValue); …}
or
#ifdef DEBUG    #define FUNC_DBG(…) printf(VA_ARGS)#else    #define FUNC_DBG(…)#endifint Foo(){    int returnValue = Bar();    FUNC_DBG(“returnValue = %d\n”, returnValue); …}
[红] 10.6 switch语句,无论何时,请写上default语句
这样别人才知道你不是忘记了。除了可以减少不必要的沟通成本,还可以进行必要的容错处理。
BAD:
switch (condition) {    case condition_1: …    break;    case condition_2: …    break;};
GOOD:
switch (condition) {    case condition_1: …    break;    case condition_2: …    break; …    default: …    break;};
11. 金玉良言
这一章记录的都是血和泪的总结,你可能一时不能理解,但请你务必记住,并遵守它。
11.0 请不要编写你自己不懂的代码,我们允许你寻求帮助,允许你拷贝,但是在你弄懂你写下的代码的实际含义之前,请不要把它合并到你的项目工程中去
11.1 代码修改的log,请遵守log规范。因为最有可能查询这条记录的人——是你自己
11.2 一些耗时操作不能放到while循环里面或者执行频率高的地方,这极有可能造成系统卡顿
11.3 写操作不要放到while循环或者执行判断高的地方,防止造成器件损坏或者其他进程或者函数无法再重写
11.4 用户功能和工厂功能要完整的隔离,互相不能影响
11.5 如果模块的子任务间是串行的,即上一个任务必须达到某个条件时,才能进入下一个任务,请设计为状态机
查看状态机知识。
11.6 中断里的过程变量不允许在中断外使用

编者
刘宇

审核

修订

致歉
限于编者水平,难免有遗漏、错误、不足之处,敬请谅解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TwLyHwh Tech

一切随缘

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值