代码编程规范

C++文件规范

关于头文件与源文件的命名

  • 文件名与类名要保持一致:如果头文件中定义了一个类,那么文件名应该与类名一致。
  • 文件名使用小写字母:通常建议使用小写字母来命名文件,因为大多数操作系统在文件系统中是大小写敏感的。
  • 避免使用数字和特殊字符:文件名中应避免使用数字和特殊字符,因为它们可能会在不同的操作系统和环境中引起问题。

关于头文件与源文件后缀

  • 头文件:C++的头文件有两种命名方式一种是.hpp ,另一种是.h
  • .h:C语言和C++语言中传统的头文件扩展名,它源自C语言。在C++中,.h扩展名仍然被广泛使用,尤其是在一些旧的代码库和跨平台的库中。
  • .hpp:C++特有的头文件扩展名,它被用来明确表示这是一个C++头文件,而不是C语言的头文件。.hpp扩展名的使用也是为了强调C++的面向对象特性和C++特有的语法
  • 源文件:C++的源文件只有.cpp 这一种命名方式。

头文件是用h还是hpp呢?

  • 在实际使用中,.h和 .hpp 没有技术上的区别 ,它们都表示头文件。选择使用哪一个主要取决于个人或团队的编码风格、项目规范以及历史习惯。有些项目可能统一使用.hpp而有些项目则可能混用 .h和.hpp。重要的是保持项目内部的一致性。

头文件重复包含问题

  1. 我们先创建一个头文件a.h,然后定义一个类a
    1
    2
    3
    4
    5
    6
    // a.h

    class A
    {

    };
  2. 然后创建一个头文件b.h,并且包含头文件a.h
    1
    2
    3
    //b.h

    #include "a.h"
  3. 最后创建一个源文件main.cpp,并包含a.hb.h
    1
    2
    3
    4
    5
    6
    7
    8
    9
    //main.cpp

    #include "a.h"
    #include "b.h"
    int main()
    {
    A a;
    }

  4. 编译运行,我们可以看到以下错误:

    [Error] redefinition of ‘class A’

    这个错误表明类 A 被重复定义了,在main.cpp文件中既包含了a.h,又包含了b.h,这两个头文件都定义了类A,所以类A会被定义两次。那么我们怎么解决这个问题呢?这里有两个方法可以使用。
  • 方法一:使用非标准的预处理指令#pragma once
    1
    2
    3
    4
    5
    6
    #pragma once//在头文件最上面加入这个指令即可

    class A
    {

    };
  • 方法二:通过宏定义 防止头文件重复包含
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #ifndef A_H
    #define A_H

    //头文件内容
    class A
    {

    };

    #endif

简单说一下这是什么意思:如果头文件A没有定义,就执行#define A_H,之后再包含该头文件时,由于已经定义了A_H,故直接跳过头文件内容。

  • 并不是所有的编译器都支持第一种写法,所以我这里只推荐第二种写法!

头文件依赖问题

使用前置声明,减少头文件的数量

  • 前置声明(Forward Declaration)是编程中的一种技术,特别是在C++和C语言中,它允许你在实际定义一个类或函数之前,声明一个类或函数的存在。这样做的好处是,它允许你在一个文件中引用另一个文件中定义的类或函数,而不需要包含后者的头文件,从而减少编译依赖和提高编译速度。
  • 不使用前置声明
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #ifndef DOG_H
    #define DOG_H

    #include "animal.h"

    class Dog {
    public:
    void bark();
    Animal* animalPtr;
    };

    #endif
  • 使用前置声明
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #ifndef DOG_H
    #define DOG_H

    class Animal; // 这是前置声明

    class Dog {
    public:
    void bark();
    Animal* animalPtr; // 使用前置声明的Animal类指针
    };

    #endif
  • 我们这里只是定义了Animal类的指针,并没有使用animalPtr,至于什么时候使用,需要我们动态地去创建内存,而前置声明没有包含头件,减少了编译时的依赖。所以使用前置声明的好处就是减少对头文件的包含
  • 为什么包含的头文件减少,能提高编译速度?
    我们都知道,现在的编译器都有两个选项,分别是编译全部编译,全部编译就是把所有的文件根据它们之间的依赖关系全都编译一遍。编译则是在编译过的基础上,把修改的文件以及相关依赖全都编译一遍

有一个头文件a.h,里面定义了类A。有100个文件依赖于a.h(#include “a.h”),如果我们对类A进行了修改,那么包含a.h的100个文件也要重新编译,这很严重地拖慢了编译速度。所以,我们要尽量减少头文件包含的数量

C++类规范

类名规范

  • 类名使用大驼峰(PascalCase)命名法。例如:MyClass、Person、Animal等,类名命名应当清晰且具有描述性,通过类名就大概能知道这个类是要做什么的,避免使用特殊字符和保留字,不要使用下划线开头。驼峰命名法

类成员声明次序

  • 类中声明次序如下:
    构造函数块析构函数块成员函数块成员变量块
  • 每一块声明次序如下:
    publicprotectedprivate
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    #ifndef PERSON_H
    #define PERSON_H

    class Person
    {
    //构造函数和析构函数一个类只能各存在一个,下面各三个只是为了方便演示声明顺序!!

    //构造函数块
    public:
    Person();
    protected:
    Person();
    private:
    Person();

    //析构函数块
    public:
    ~Person();
    protected:
    ~Person();
    private:
    ~Person();

    //成员函数块
    public:
    void func();
    protected:
    void func();
    private:
    void func();

    //成员变量块
    public:
    int m_val;
    protected:
    int m_val;
    private:
    int m_val;

    };
    #endif

构造函数的职责

  • 构造函数中只进行那些没有实际意义的初始化(非业务逻辑的初始化)。
  • 在C++11标准以上,使用初始化列表 进行类成员变量的初始化。
  • 尽量在 Init()方法集中初始化业务逻辑数据。

下面是一种规范的类的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef PERSON_H
#define PERSON_H

class Person
{
public:
Person(string name,unsigned short int age);
~Person();
private:
void init();//逻辑初始化成员函数
bool isAdult();//判断是否成年
private:
string m_name;// 姓名
unsigned int short m_age;// 年龄
bool m_isAdult; //是否成年
};
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Person::Person(string name,unsigned short int age):m_name(name),m_age(age)//初始化列表进行初始化
{
//下面是错误的做法
//this.m_isAdult=isAdult();
//下面是正确的做法
init();
}

Person::~Person()
{
}

void Person::init()
{
this.m_isAdult=isAdult();
//其他逻辑初始化,都写下面
//……
}

bool Person::isAdult()
{
return m_age>=18?true:false;
}

结构体使用

  • 只有数据时,才使用struct。
  • 类和结构体的成员变量命名不同。
1
2
3
4
5
6
struct PosInfo
{
int _x;
int _y;
int _z;
}

继承

  • 优先使用组合,如果必须要使用继承的话,只使用public继承。
  • 尽量不使用多重继承,如果必须使用的话,多重继承中只有一个基类是有实现的,其他的都只能是接口类。

在面向对象中,一个比较重要的设计原则就是组合优于继承

  • 继承可能会导致代码的脆弱性,因为基类的变化可能会影响所有派生类。此外,继承还可能导致代码的紧密耦合。使用组合的类通常更加容易理解和维护,因为它们之间的关系更清晰,耦合度更低。
  • public继承是最常用的继承方式,因为它保持了基类接口的完整性,同时允许派生类扩展或修改基类的行为。

格式规则

使用空格还是制表符?(Spaces vs. Tabs)

  • 只使用空格,并且每次缩进4个空格。

括号格式{}

  • 左右括号都单独起一行
1
2
3
4
void func1()
{ //单独一行

} //单独一行

命名约定

  • 函数命名、变量命名、文件命名应具有描述性,不要过度缩写。
  • 类型和变量应该是名词,函数名可以用“命令性”动词。

变量命名

  • 普通变量名:一律小写,单词间以下划线相连,如
    my_exciting_local_variable。
  • 类的成员变量:m_开头,如m_my_exciting_member_variable。

函数命名

  • 普通函数:采用大驼峰命名法,函数名以大写字母开头,每个单词首字母大写,没有下划线。如BubbleSort(),PrintInfo()等。
  • 类的成员函数:采用小驼峰命名法,每个单词小字母小写,后面的单词首字母大写,无下划线。如getName(),setAge(int age)等。

无论是普通函数,还是成员函数,都是以下格式组成:

  • 单个动词:例如:erase()、reserve()、size()等。
  • 动词+名词:例如:getName()、setName()等。
  • 注:在Qt中,类成员函数的get方法命名与C++传统的命名风格不一样。Qt中get方法是没有“get”这个动词的,如在Qt中的QPoint类中,获取x,y坐标的函数是x()和y() ,而不是getX()和getY()。

宏命名

  • 全部使用大写字母
  • 单词之间使用下划线
1
2
3
4
#define PI_ROUNDED 3.0
#define MAX_LENGTH 10
#define BUFFER_SIZE 1024
……

枚举命名

  • 枚举属于类型,采用大驼峰命名法
1
2
3
4
5
6
7
enum KeyLength
{
BITS_1k = 1024,
BITS_2k = 2048,
BITS_3k = 3072,
BITS_4k = 4096,
};

Doxygen注释规则

项目注释

一般放在main.cpp 文件的顶部。

1
2
3
4
5
6
///////////////////////////////////////////////////////////////////////////
/// @mainpage 项目注释
/// @author 作者
/// @version 版本
/// @date 2024 年 12 月 12 日
///////////////////////////////////////////////////////////////////////////

文件注释

一般放在.h头文件 的顶部。

1
2
3
4
5
///////////////////////////////////////////////////////////////////////////
/// @file 文件名
/// @brief 简介
/// @details 细节
///////////////////////////////////////////////////////////////////////////

变量与常量注释

变量与常量的注释风格一样

1
2
3
4
5
6
/// 代码前注释
int a;

int b; ///< 代码后注释

int c; /*! Qt风格的注释 */

类定义注释

一般放在类定义 的上方。

1
2
3
4
///////////////////////////////////////////////////////////////////////////
/// @brief 类的简单概述
/// @details 类的详细概述
///////////////////////////////////////////////////////////////////////////

函数定义注释

一般放在函数定义 的上方。

1
2
3
4
5
6
7
///////////////////////////////////////////////////////////////////////////
/// @brief 函数简介
///
/// @param 形参 参数说明
/// @param 形参 参数说明
/// @return 返回说明
///////////////////////////////////////////////////////////////////////////