week01-C++编码规范

  1. 1. C++编码规范
    1. 1.1. 1 命名
      1. 1.1.1. 1.1 文件命名
      2. 1.1.2. 1.2 类命名
      3. 1.1.3. 1.3 其他类型命名
      4. 1.1.4. 1.4 变量命名
      5. 1.1.5. 1.5 函数命名
      6. 1.1.6. 1.6 宏命名
      7. 1.1.7. 1.7 全局变量
      8. 1.1.8. 1.8 名称空间命名
      9. 1.1.9. 1.9 不允许使用My或自己的姓名作为以上命名的前缀
    2. 1.2. 2 头文件
      1. 1.2.1. 2.1 头文件包含保护
      2. 1.2.2. 2.2 前置声明
      3. 1.2.3. 2.3 头文件包含顺序
    3. 1.3. 3 类
      1. 1.3.1. 3.1 初始化
      2. 1.3.2. 3.2 拷贝构造函数、赋值运算符
      3. 1.3.3. 3.3 继承
      4. 1.3.4. 3.4 运算符重载
      5. 1.3.5. 3.5 访问权限声明
      6. 1.3.6. 3.6 友元
    4. 1.4. 4 函数与实现
      1. 1.4.1. 4.1 参数
      2. 1.4.2. 4.2 内联函数
      3. 1.4.3. 4.3 异常处理
      4. 1.4.4. 4.4 类型编程
      5. 1.4.5. 4.5 宏
      6. 1.4.6. 4.6 类数据成员的引用
    5. 1.5. 5 注释
      1. 1.5.1. 5.1 文件头
      2. 1.5.2. 5.2 代码注释
      3. 1.5.3. 5.3 注释语言
      4. 1.5.4. 5.4 预处理宏注释
    6. 1.6. 6 格式
      1. 1.6.1. 6.1 对齐与缩进
      2. 1.6.2. 6.2 空格
      3. 1.6.3. 6.3 换行
    7. 1.7. 7 作用域
      1. 1.7.1. 7.1 嵌套类
      2. 1.7.2. 7.2 局部变量
      3. 1.7.3. 7.3 全局变量
      4. 1.7.4. 7.4 名称空间(namespace)
    8. 1.8. 8 模板
      1. 1.8.1. 8.1 模板声明
      2. 1.8.2. 8.2 模板适用范围
      3. 1.8.3. 8.3 模板中的嵌套类
      4. 1.8.4. 8.4 模板继承
    9. 1.9. 9 内存
      1. 1.9.1. 9.1 优先使用静态内存
      2. 1.9.2. 9.2 其次为栈内存
      3. 1.9.3. 9.3 使用智能指针和容器管理堆内存
    10. 1.10. 10 跨平台
      1. 1.10.1. 10.1 路径分隔符
      2. 1.10.2. 10.2 数据类型
      3. 1.10.3. 10.3 系统 API
      4. 1.10.4. 10.4 字符串
    11. 1.11. 11 其它规范

C++编码规范

遵循编码规范,可减少低级问题,提升项目的稳定性
提交代码审查前,养成自审代码的习惯


1 命名

1.1 文件命名

  • 文件名全部使用小写字母
    示例: kctlinechartview.h

1.2 类命名

  • 类的命名使用前缀+驼峰命名法
  • 当一个类需要暴露给其它工程使用时,应加上工程约定的前缀,前缀全部使用大写字母
  • 类命名尽量使用完整英文单词,除非是常见的缩写
    1
    KCTLineChartView
  • Com接口命名在普通前缀前加 I 作为标志
    示例: IKCTLineChartView

1.3 其他类型命名

  • struct、typedef 与enum 的命名原则与类相同
  • enum成员采用大写字母开头的驼峰写法,且应在命名中体现enum名称,具体位置不做强制要求,但同一个enum内的命名规则要保持一致
    1
    2
    3
    4
    5
    6
    enum ActionType
    {
    InsertActionType,
    DeleteActionType,
    MoveActionType
    };

1.4 变量命名

  • 变量命名使用小写字母开头的驼峰写法,建议加类型信息前缀,命名应使用能表达变量涵义的完整英文单词。
    示例:
    1
    2
    3
    int nIndex = 0; //整数类型n
    KCTLineChartView *pLineChartView = nullptr; //普通指针类型p
    std::unique_ptr<CSomeObject> spObject = new CSomeObject(); //智能指针类型sp
  • 仅允许在for单层循环中的 i 和表示坐标的x、y、z上使用单字母变量,其他情况下不允许使用
  • 非static 的类成员变量前加上小写字母 m_ 前缀,static 的类成员变量前加上小写字母 s_ 前缀,这两种情况下变量名的第一个单词字母小写后面单词字母大写
    1
    2
    3
    4
    5
    6
    class GoodClass
    {
    private:
    int m_dataMember;
    static int s_staticDataMember;
    };

1.5 函数命名

  • 除构造函数和析构函数外,类成员函数使用小写字母开头的驼峰写法
  • 非类成员函数使用大写字母开头的驼峰写法
    示例:
    1
    2
    lineChartView->lineCount();
    GetObjectCountInDocument();
  • 名词属性的类成员函数命名分两种情况
    • 若欲获取的信息当成返回值返回,则命名方式为 名词或名词+修饰语 的样式
    • 若欲获取的信息通过某个参数返回,则命名方式为get+名词(+修饰语)的样式
      示例 :
      1
      2
      3
      4
      QString title = lineChartView->getTitle();
      lineChartView->title();
      lineChartView->subViewAtIndex(index);
      lineChartView->getSubViews(&outPointer);
  • 动词属性的类成员函数命名方式为动词+宾语,若宾语是this, 应省略
    示例:
    1
    2
    3
    lineChartView->removeAllSubViews();
    lineChartView->setTitle(L"Hello World");
    lineChartView->initialize();
  • 形容词属性的类成员函数命名方式为 is/has等修饰语+形容词/名词
    示例:
    1
    2
    lineChartView->isVisible();
    lineChartView->hasTitle();
  • 返回对象指针的函数若内部分配了内存,应使用 copy, create 等关键字把该信息反映在函数名上
    示例:
    1
    2
    lineChartView->subViews(); //未重新分配内存
    lineChartView->copySubViews(); //重新分配了内存

1.6 宏命名

  • 原则上宏的命名全部大写,如有必要单词间用下划线分隔。如无必要,不把一段代码定义成宏
    示例
    1
    #define SOMETHING_USED

1.7 全局变量

  • 关于使用全局变量的注意事项见后文
    • 全局变量前加小写字母 g_ 前缀
    • static全局变量前加小写字母 gs_ 前缀
      示例:
      1
      2
      bool g_inFileOpening = false;
      static bool gs_staticInFileOpening = true;

1.8 名称空间命名

  • 名称空间使用简短的全小写英文单词命名
    1
    2
    3
    4
    namespace chart
    {
    ....
    }

1.9 不允许使用My或自己的姓名作为以上命名的前缀

  • 禁止下列写法
    1
    2
    3
    4
    5
    class MyStringProject
    {
    public:
    ...
    };

2 头文件

2.1 头文件包含保护

  • 在所有头文件中均使用 #ifndef + #define + #endif 来避免该文件被重复包含
  • 宏的命名方式为 PROJECTNAME_FILENAME_H

示例

  • KProject 项目有头文件名为 kheader.h,则文件头部加上
    1
    2
    #ifndef __KPROJECT_KHEADER_H__
    #define __KPROJECT_KHEADER_H__
  • 末尾加上
    1
    #endif //__KPROJECT_KHEADER_H__

2.2 前置声明

  • 当使用一两个前置声明就能编译通过时,不要在头文件里包含另一个头文件

2.3 头文件包含顺序

  • 在代码文件里包含多个头文件时,应按照如下顺序将头文件分组,每组之间使用空行隔开:
    • 预编译头文件,通常是stdafx.h
    • 与代码文件同名的头文件
    • 系统头文件
    • 其他工程头文件
    • 本工程其他头文件

示例

  • KProject 工程中用到如下头文件
    • kclass.cpp实现了声明在kclass.h内的类
    • C++标准库中的vector容器
    • ExternalProject 工程中的externalclass.h和externalutils.h
    • KProject工程中的kfriendclass.h、kfileiohelper.h、kstringutils.h
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      #include "stdafx.h" // 预编译头文件,通常是stdafx.h
      #include "KClass.h" // 与代码文件同名的头文件

      #include <vector> // 系统头文件

      // 其他工程的头文件
      #include "externalclass.h"
      #include "externalutils.h"

      // 本工程其他头文件
      #include "kfriendclass.h"
      #include "kfileiohelper.h"
      #include "kstringutils.h"

3 类

3.1 初始化

  • 类的所有成员变量必须初始化,只有成员变量没有函数的类,必须定义默认构造函数
  • 除非有特殊需求,在类的构造函数中仅进行不涉及具体功能的初始化操作,例如为成员变量赋零
  • 较复杂的初始化操作,应放在一个单独的 init() 方法中,由类实例的创建者负责调用
    解释:
    构造函数内难以报告错误,且构造函数中对虚函数的调用不会派发给子类,所以较复杂的初始化操作应在构造完成之后进行
  • 若没有明确的将单参构造函数用于隐式类型转换的需求,应使用 explicit 关键字

3.2 拷贝构造函数、赋值运算符

  • 若没有明确的对类进行拷贝的需求,应在private 段中声明拷贝构造函数和赋值运算符

3.3 继承

  • 只使用 public 继承
  • 语义明确的时候从父类继承具体实现,其他时候从抽象接口继承
  • 若类有虚函数,则析构函数也定义为函数
  • 父类中声明为 virtual 的函数,子类声明中要明确标明为 virtual 以及 override

3.4 运算符重载

  • 尽量避免运算符重载,除非是为了在容器类中使用而必须实现的代码

3.5 访问权限声明

  • 类声明中按 public, protected, private 的顺序声明函数和变量
  • 每个关键字仅占用一段,每一段中的声明顺序为:
    • typedef, enum
    • Q_OBJECT及类似声明
    • 嵌套类
    • 常量
    • 构造函数、析构函数、成员函数、数据成员

3.6 友元

  • 友元的定义和友类的定义应放在同一个文件中

4 函数与实现

4.1 参数

  • 参数排列顺序为输参数在,输参数在
  • 引用作为输入参数时应配合 const 使用,不使用缺省参数
  • 不允许一个参数作为传参数,作为传参数
  • 复杂类型参数,通过加引用避免内存拷贝

4.2 内联函数

  • 内联函数不超过10行,推荐1行的函数进行 inline
  • 使用循环switch...case进行递归
  • 类定义体内实现的函数,不要inline 关键字

4.3 异常处理

  • 尽量避免使用异常try...catch),优先考虑基于返回错误码处理异常

4.4 类型编程

  • 不使用运行时类型信息
  • 明确使用 static_cast, const_cast 进行类型转换,避免使用 reinterpret_cast,小心使用 dynamic_cast

4.5 宏

  • 尽量使用 constenuminline 替代 #define

4.6 类数据成员的引用

  • 避免成员函数返回指向类成员的指针或引用

5 注释

5.1 文件头

文件头注释应包括文件名、创建者、创建时间、功能描述和版权信息

1
2
3
4
5
6
/* ---------------------------
// 文件名 : kpainter.cpp
// 创建者 : xxwms
// 创建时间 : 2024-11-05
// 功能描述 : 硬件加速绘制
--------------------------- */

5.2 代码注释

  • 注释使用 // 风格,应简洁清晰
  • 非必要,不写注释
  • 需要写注释的场景:
    • 故意违背编码规范之处
    • 分成多步完成的任务
    • 较复杂的逻辑
    • 和常理不符的代码
    • 比较重要,需要引起注意的地方

5.3 注释语言

  • 默认注释语言为英文
    • 注释中的工程师姓名
  • 只在仅两种情形下把姓名加入注释中:
    • 文件头中的创建者信息
    • TODO 注释时
      1
      // TODO(tommy_zhang): 删掉这里的全局变量!

5.4 预处理宏注释

  • 中间代码段较长的 #else#endif 之后用注释标明宏的名字
    1
    2
    3
    4
    5
    #if TARGET_PLATFORM_WINDOWS
    // ...
    #else // TARGET_PLATFORM_WINDOWS
    // ...
    #endif // TARGET_PLATFORM_WINDOWS

6 格式

6.1 对齐与缩进

  • 使用 Tab 进行对齐
  • 花括号上下对齐不允许将左花括号放在 if 等语句的末尾
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    void KFunction()
    {
    if (...)
    {
    // ...
    }
    else
    {
    // ...
    }

    do
    {
    // ...
    } while (...);

    for (int i = 0; i < 10; ++i)
    {
    // ...
    }
    }
  • 构造函数初始化列表和多个基类的每一行逗号放前面,和冒号对齐
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    KGroupGrid::KGroupGrid(QWidget *pParent)
    : KWidget(pParent)
    , m_scrollBar(Qt::Vertical)
    , m_nHeaderHeight(0)
    , m_itemSize(80, 64)
    , m_showToolTip(false)
    , m_showSeparatorLine(true)
    , m_scrollBarPolicy(Qt::ScrollBarAsNeeded)
    {
    }
  • 只有一行代码的 if 语句不加花括号
    1
    2
    3
    4
    if (inputType == badType)
    doSomethingBad();
    else
    doSomethingElse();
  • if 语句中某一个分支一行以上代码时,所有 if/else 分支都要加花括号
    1
    2
    3
    4
    5
    6
    7
    8
    9
    if (inputType == badType)
    {
    doSomethingBad();
    }
    else
    {
    doSomethingElse1();
    doSomethingElse2();
    }
  • 推荐一行代码不超过120个字符
  • 函数参数表过长时,应换行,返回值类型、左括号应保持与函数名同一行,具体换行方案有两种:
    1
    2
    3
    4
    5
    6
    HRESULT KClass::method(ParameterOne param1,
    ParameterTwo param2,
    ParameterThree param3)

    HRESULT KClass::method(ParameterOne param1,
    ParameterTwo param2, ParameterThree param3)
  • 函数类型为 const 时,const 关键字与最后一个参数同行
    1
    2
    3
    HRESULT KClass::constMethod(ParameterOne param1,
    ParameterTwo param2,
    ParameterThree param3) const
  • 所有预编译宏全顶格对齐
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    void KClass::methodWithMacros()
    {
    static int integer1 = 0;
    integer1++;
    if (integer >= 10)
    {
    #if PLATFORM_1
    integer1 = 1;
    #else
    integer1 = 2;
    #endif
    }
    }
  • 分为多行的布尔表达式中 &&|| 置于行首
  • 换行应在运算符优先级最低的地方进行,尽可能避免把配对的括号成两行
  • 当条件比较复杂时,应将高优先级的运算用括号明确标识出来
    1
    2
    if ((valueGood >= somethingGood || otherCondition)
    &&(valueBad + 1) <= somethingReallyBad)
  • 命名空间 namespace内容不缩进

6.2 空格

  • 函数调用的括号前后不加空格,参数之间逗号后加空格
    1
    void KFunction(ParameterOne para1, ParameterTwo para2)
  • ifwhilefor等关键字与括号间加一个空格,括号后不加空格
    1
    2
    3
    if (nullptr == thePointer)

    for (int index = 0; index < maxIntValue; index++)
  • 使用Tab键无法刚好对齐时,用空格补齐
  • 没有参数的函数括号内不加空格
  • 二元运算符前后各加一个空格
  • 自增减运算符与分号,变量间都不加空格
    1
    2
    3
    if (inputType1 == goodType && inputType2 == badType)

    for (int index = 0; index < MAX_INT; index++)
  • 引用符号(. ->前后不加空格
    1
    spA->value();
  • 地址、引用运算符(*, &后不加空格
    1
    *pA = 1;
  • 声明指针类型的 *前加一空格,后边不加空格
    1
    KCircle *pCircle = nullptr;

6.3 换行

  • 禁止滥用换行;禁止重复换行
  • 建议使用换行的场景:
    • 文件级:include 分组间、命名空间边界
    • 类级:访问权限段间、成员类型间
    • 函数间:每个函数实现之间
    • 函数内:独立功能逻辑步骤之间
    • 特殊块:预编译宏、异常处理前后

7 作用域

7.1 嵌套类

  • 不需要暴露嵌套类作为接口的时候,将嵌套类声明为 private

7.2 局部变量

  • 局部变量应在将要使用时进行声明,声明的同时初始化
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    void SomeMagicalFunction()
    {
    const int magicNumber = 1;
    int integer1 = magicNumber;
    integer1++;
    if (integer1 <= 0)
    DoSomethingWeird();

    float floatingNumber1 = 1.0f;
    float *floatingPointer = &floatingNumber1;
    if (floatingPointer == NULL)
    HowCouldThisHappen();
    }

    void hello()
    {
    }

7.3 全局变量

  • 除非特殊情况,尽量不使用 Class 类型的全局变量,不使用 Class 类型的 static 类数据成员
  • 可以用单例模式替代 Class 类型的全局变量
  • 全局的字符串变量使用 C 风格的 char,不使用各种字符串类

7.4 名称空间(namespace)

  • 允许在 cpp 文件中使用匿名名称空间进行保护,不允许在头文件中使用匿名名称空间
  • 避免使用 using namespace 将一个名称空间中的所有名称全部导入

8 模板

8.1 模板声明

  • 使用 typename 关键字声明模板,不使用 class 关键字

8.2 模板适用范围

  • 一般情况下仅使用模板来实现容器类或通用算法,如果需要用作其他用途,请增加注释说明必要性

8.3 模板中的嵌套类

  • 模板中使用嵌套类时,加上 typename 关键字声明
    1
    2
    3
    4
    5
    6
    template<typename T>
    void someFunction(const T& container)
    {
    typename T::Iterator iter(container.begin());
    iter.next();
    }

8.4 模板继承

  • 在模板派生类中调用模板基类函数时,明确在函数调用前声明该函数属于基类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    template<typename T>
    class DerivedClass : public BaseClass<T>
    {
    public:
    void doAction()
    {
    BaseClass<T>::doBaseAction();
    }
    };

9 内存

9.1 优先使用静态内存

  • 编译期能够确定常量数组,应声明为 static
    1
    2
    3
    4
    5
    void func(int type)
    {
    static const int someInt[] = {80, 40, 40, 50, ... 94, 94, 47};
    // ...
    }

9.2 其次为栈内存

  • 编译期能够确定长度的非常量数据,应使用内存
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void func(int type)
    {
    const int length = 1024;
    unsigned char buffer[length + 1];
    DWORD dwReadSize = length;
    while (::ReadFile(hFile, buffer, length, &dwReadSize, NULL))
    {
    // ...
    dwReadSize = length;
    }
    }

9.3 使用智能指针和容器管理堆内存

  • 单个堆对象的生命周期用 std::unique_ptr 来管理
  • 连续的内存空间用 std::vectorQVector 来分配和管理,不允许在代码中出现 new[]delete[]显式调用
    1
    2
    3
    4
    5
    6
    7
    8
    void func(int type)
    {
    std::unique_ptr<CSomeObject> spObject(new CSomeObject());

    int bufferLength = getBufferLength();
    std::vector<SomeType> vecBuffer(bufferLength);
    readBuffer(&vecBuffer[0], bufferLength);
    }
  • 字符串用 std::basic_string<T>QString 来管理
  • delete 应该出现在析构函数或用于清理内存的函数之外
  • 禁止使用 malloc/free分配和释放内存

10 跨平台

10.1 路径分隔符

  • 禁止代码里拼路径时写死路径分隔符,依赖Qt的模块可使用 QDir::separator()

10.2 数据类型

  • 禁止使用平台不一致的数据类型,如 Windows 平台 long 为固定的4字节,而 Unix 则通常32位下为4字节,64位下为8字节。

10.3 系统 API

  • 优先使用跨平台的 API,如QT;尽量避免使用Windows系统的API,如需要必须加平台宏进行隔离

10.4 字符串

  • 优先使用跨平台的 WCHAR 字符串

11 其它规范

  • 常量定义优先使用constexpr,而不是const
  • 非函数内部的常量定义优先考虑大写
  • 禁止出现重复代码(重复2次以上),重复代码要抽取复用
  • 尽量避免函数过于臃肿(超过500行)
  • 多线程读写的变量加atomic或其他原子修饰符
  • 禁止使用高危函数
  • 指针变量赋值为空使用nullptr,不要用NULL
  • 禁止代码中出现中文硬编码,注释除外
  • 设置大小,位置等UI参数时,要考虑DPI缩放处理
  • 禁止出现无用的变量或代码