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。重要的是保持项目内部的一致性。
头文件重复包含问题
- 我们先创建一个头文件a.h,然后定义一个类a
- 然后创建一个头文件b.h,并且包含头文件a.h
- 最后创建一个源文件main.cpp,并包含a.h和b.h
1 2 3 4 5 6 7 8 9
|
#include "a.h" #include "b.h" int main() { A a; }
|
- 编译运行,我们可以看到以下错误:
[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; };
#endif
|
- 我们这里只是定义了Animal类的指针,并没有使用animalPtr,至于什么时候使用,需要我们动态地去创建内存,而前置声明没有包含头件,减少了编译时的依赖。所以使用前置声明的好处就是减少对头文件的包含。
- 为什么包含的头文件减少,能提高编译速度?
我们都知道,现在的编译器都有两个选项,分别是编译和全部编译,全部编译就是把所有的文件根据它们之间的依赖关系全都编译一遍。编译则是在编译过的基础上,把修改的文件以及相关依赖全都编译一遍。
有一个头文件a.h,里面定义了类A。有100个文件依赖于a.h(#include “a.h”),如果我们对类A进行了修改,那么包含a.h的100个文件也要重新编译,这很严重地拖慢了编译速度。所以,我们要尽量减少头文件包含的数量。
C++类规范
类名规范
- 类名使用大驼峰(PascalCase)命名法。例如:MyClass、Person、Animal等,类名命名应当清晰且具有描述性,通过类名就大概能知道这个类是要做什么的,避免使用特殊字符和保留字,不要使用下划线开头。驼峰命名法
类成员声明次序
- 类中声明次序如下:
构造函数块→析构函数块→成员函数块→成员变量块
- 每一块声明次序如下:
public→protected→private1 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) { 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)
括号格式{}
命名约定
- 函数命名、变量命名、文件命名应具有描述性,不要过度缩写。
- 类型和变量应该是名词,函数名可以用“命令性”动词。
变量命名
- 普通变量名:一律小写,单词间以下划线相连,如
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 文件的顶部。
文件注释
一般放在.h头文件 的顶部。
变量与常量注释
变量与常量的注释风格一样
1 2 3 4 5 6
| int a;
int b;
int c;
|
类定义注释
一般放在类定义 的上方。
函数定义注释
一般放在函数定义 的上方。