C++ 编码规范小结
前言
最近体会到真实的一句话吧,年轻人终归是年轻人,菜就是菜啊,最近我感觉我真的好菜啊。
最近写代码,学习,经历了一些事情,理解到编码规范、Review的重要性,了解到建立一套自己的一些编码行为习惯是一件对于程序员来说较为重要的行为习惯,越早建立,对于未来的约束和发展越好,因此,在学习了一部分编码习惯后,在此提供一套自己的编码习惯作为给各位的参考,大家可以按需对部分的习惯进行修改,不必拘泥于我的习惯,个人提供仅供参考,与诸君共勉。
1. 命名约定
- 类名、函数名、项目名、文件名等 不使用缩写名字。但专用的名字则使用缩写,如Http,Cpu,Dvd,Gui,Guid等,且除第一个字母大写外,其余字母小写:
1. void exportHtmlSource()
- 类名、typedef、模板类必须大写开头:
1. class FrameVecotr;
2. typedef unsigned long BitMask;
3. template<typename T>
class MyClass {};
- 常量和宏必须全部大写,并使用下划线隔开单词:
1. const int ITERATIONS = 25;
2. #define RADIUS 30;
- 变量名必须小写开头:
int lineWidth
对于枚举类型的值
- 推荐使用强枚举类型的值,比如:
1. enum class Color { Blue, Orange }; Color c = Color::Blue;
- 非强类型枚举值必须全部大写,以枚举名为前缀,并使用下划线隔开单词,比如:
enum { Insensitive, Sensitive };
// 要改为
enum { CASE_INSENSITIVE, CASE_SENSITIVE };
- 模板推荐使用typename关键字。如果类型一定不是基础类型,则可使用class:
template<typename T> VS template<class PixelType>
- 类成员变量使用m_前缀:
std::string m_name;
bool m_visible;
类的getter成员函数命名规则
- 形容词以is-为前缀,比如:
isEmpty(), isChecked();
- 修饰名词的形容词没有前缀,比如:
// 使用
scrollBarsEnabled();
// 而不是
areScrollBarsEnabled();
- 动词+名词时,动词没有is-前缀,也不使用第三人称(-s),比如:
// 使用
acceptDrops();
// 而不是
isAcceptsDrops();
- 考虑到自己写的学习项目大部分代码会使用Qt,还是保持使用Qt的风格,名词不使用get-为前缀,比如:
width();
name();
autoCompletion();
- 名词不带前缀容易混淆时,考虑加上is-或has-前缀,比如:
// 使用
isDialog();
// 而不是
dialog();
// dialog()让人误以为返回个对话框对象。
// 使用hasFocus(),而不是focus(),focus()让人误以为返回个焦点对象。
- 类的setter成员函数命名规则:名称来源于getter,只是去掉了is-等前缀,在前面加上了set,例如:
setEnabled();
setScrollBarsEnabled();
setFocus();
- 平时写GUI控件这类代码的时候,相关的变量名推荐以控件类型为后缀
void* mainWindow;
void* mainForm;
void* fileMenu;
void* exitButton;
- 推荐列表容器对象的后缀使用List(单向列表)或Array(双向列表):
std::list vertexList;
std::array pointArray;
- 表示数据的变量使用n为前缀:
int nPoints;
int nLines;
- 避免使用否定的bool变量:
bool isFound;
// 而不是
bool isNotFound;
- 异常的子类加上Error或Exception后缀,如:
class AccessError;
- 推荐使用对称性单词作为函数名,如下:
add/remove, create/destroy, start/stop, insert/delete, increment/decrement,
old/new, begin/end, first/last, up/down, min/max, next/previous, open/close,
show/hide, suspend/resume
- 优先使用单词"make"而不是create/build/compute来给创建函数命名。但如果有使用习惯,按惯例命名,例如在工厂设计模式里,使用"create"命名。
2. 文件
-
优先使用.h作为头文件扩展名,如果头文件里有模板,将使用.hpp作为头文件扩展名。如果需要定义较多的内联函数,可将内联函数放到.inl文件里。
-
源文件扩展名必须是.cpp。
-
通常头文件里只定义一个类,但如果相关的类比较小,也可以在同个文件里定义。
-
文件名使用项目名+文件里的主要类的名字,所有单词为小写且使用下划线连接,比如gui_base_combo_box.h和gui_base_combo_box.cpp。(纯个人习惯)
-
使用#define保护头文件,而不是#pragma once。格式如下:H_H,例如#define WIDGETS_COMBO_BOX_H_H
-
前置声明:
- 尽量避免前置声明那些定义在其他项目中的类型。
- 函数不使用前置声明,而是使用#include。
- 类模板优先使用#include。
-
#include
- 使用#include “”引用解决方案里的头文件,使用#include <>引用系统、标准库和第三方库头文件。
- 引用的头文件路径使用”/”而不是”\”,不能使用”.”或”…”相对路径:#include <boost/any>
- 由于文件名已经带了项目的名字,不存在文件名重复的情况,大部分情况下不需要使用全路径。但如果引用项目内的子模块的头文件,则仍然需要使用全路径。
-
#include头文件使用分组方式,使用空行隔开,为了及时发现项目内部错误,引用顺序如下:
- 源文件所对应的头文件
- 本项目的头文件
- 解决方案里的其它项目的头文件
- C系统头文件
- C++系统头文件
- 引用的第三方库头文件
// 例如:
#include "foo/public/fooserver.h" // 优先位置
#include "foo/public/bar.h"
//空一行
#include "base/basictypes.h"
#include "base/commandlineflags.h"
//空一行
#include <sys/types.h>
#include <unistd.h>
//空一行
#include <hash_map>
#include <vector>
//空一行
#include <boost/any>
- 头文件和源文件顶部都需要增加版权声明,如下:
3. 项目
- 必须有一个<项目名>.h或<项目名>_global.h的头文件,用于显明该项目内部的全局接口,该头文件也用于预编译头文件。如果项目有对外公开的接口,需要创建<项目名>_export.h头文件,用于声明导出。
- 项目有全局的私有接口,则项目需要有一个<项目名字>_private.h的头文件,私有接口不使用__declspec(dllexport)等声明进行导出。私有接口自己在项目内部使用即可。
4. 类
- 类名使用Zs前缀。(Zs代表 Zty Soft)(纯个人习惯)
- 所有的对外SDK接口类都定义在ZsBase项目里,所以SDK的接口类命名为Zs++Base。非SDK的接口类也延续使用这个命名方式。
- 单参数的构造函数使用explicit,防止隐式构造。C++ 11 explicit构造函数可以禁止初始化列表构造,例如:
class Foo
{
explicit Foo(int x);
explicit Foo(int x, double y);
};
void Func(Foo f);
- 构造函数里禁止调用虚函数,因为此时函数无法实现多态。
- 类有虚函数时必须把析构函数声明为虚函数。
- 在未明确虚函数的作用时尽量少定义虚函数。
- 所有继承都应该是public的,否则使用成员变量的方式实现。
- 慎用多继承。但如果用多继承,除第一个父类外,其它父类都必须是纯接口类。
- 除了static const类型成员外,成员变量推荐使用私有权限,不要过度使用protected。
- 多用override和final关键字来识别虚函数,包括虚析构函数。
- 如果定义了复制构造函数和赋值函数,则需考虑是否也定义移动构造函数和移动赋值函数(C++ 11的移动语句)。
5. 函数
- 内联函数的代码不超过10行。如果内联函数的代码不超过5行可以在类内定义,否则在类外定义。
- 函数重载:
- 重载不是由参数个数决定,否则可使用std::vector或初始化列表实现
- 重载不是由参数类型决定,解决参数类型重载可以使用函数名+类型的方式实现,比如appendInt(int), appendDouble(double)等。
- 默认参数:
- 只允许在非虚函数使用默认参数。
- 尽可能不使用默认参数,因为默认参数可以通过函数重载实现。在不产生歧义的情况下,才使用默认参数,有任何疑惑就使用重载。
- lambda函数:
- 虽然编译器能自动识别lambda的返回值,但建议在lambda表达式写上后置返回值。但比较复杂的返回值时可以不写。
- 不使用默认捕捉(即[=],[&]),要列出具体捕捉对象。
- 函数参数要直观,让人易于理解。比如:
QSlider *slider = new QSlider(12, 18, 3, 13, Qt::Vertical, 0, "volume");
//参数代表什么?这句代码不如以下直观
QSlider *slider = new QSlider(Qt::Vertical);
slider->setRange(12, 18);
slider->setPageStep(3);
slider->setValue(13);
slider->setObjectName("volume");
- lambda函数
- 建议不使用bool参数来控制函数执行,容易让人疑惑。比如:
widget->repaint(false); //false是指不重绘吗?
// 实际上可以分为以下两个函数,或者使用枚举值来代替bool
widget->repaint();
widget->repaintWithoutErasing();
- 函数参数:
- 推荐传引用参数,少传指针,引用可以避免空指针和指针偏移操作。
- 输入的引用或指针参数使用const修饰。比如:bool QWidget::isVisibleTo(const QWidget *ancestor) const;
- 输入参数类型大小小于16个字节并且是POD类型,使用传值方式。比如:setWidth(int w);没必要写成setWidth(const int& w);
- 输出参数使用指针而非引用,因为使用指针可使用户代码可读性更好。比如:getWidth(&w)比getWidth(w)更能说明参数是输出的。
- 有多个输出参数时,考虑将参数做为简单的结构输出。比如color.getHsv(&h, &s, &v);可改成:struct Hsv { int h, s ,v }; Hsv v = color.getHsv();
- getter等不会改变类成员变量的函数声明为const。比如:bool QWidget::isVisible() const;
// 推荐传引用参数,少传指针,引用可以避免空指针和指针偏移操作。
// 输入的引用或指针参数使用const修饰。比如:
bool QWidget::isVisibleTo(const QWidget *ancestor) const;
// 输入参数类型大小小于16个字节并且是POD类型,使用传值方式。比如:
setWidth(int w);
// 没必要写成
setWidth(const int& w);
// 输出参数使用指针而非引用,因为使用指针可使用户代码可读性更好。比如:
getWidth(&w);getWidth(w);
//比getWidth(w)更能说明参数是输出的。
// 有多个输出参数时,考虑将参数做为简单的结构输出。比如
color.getHsv(&h, &s, &v);
// 可改成:
struct Hsv { int h, s ,v }; Hsv v = color.getHsv();
// getter等不会改变类成员变量的函数声明为const。比如:
bool QWidget::isVisible() const;
6. 命名空间
- 使用ZS_BEGIN_NAMESPACE和ZS_END_NAMESPACE两个宏进行声明命名空间。通常类名已能区分不同的实体,但为了防止少见的名字冲突才引入命名空间。
- 禁止使用using引入命名空间,using namespace XXX会污染命名空间。
- 禁止在头文件里使用命名空间别名,namespace baz = ::foo::bar::baz;
- 在源文件里可以使用匿名命名空间,但头文件里禁止使用匿名命名空间。
- 禁止使用std作为项目的命名空间。
7. 其他约定(杂记)
- GUI项目群里使用4个空格的缩进方式,不要使用tab。可通过VS Tools/Options/Text Editor/C/C++/Tabs进行设置。
- 右值引用:除在移动构造函数和移动赋值操作函数里使用右值引用,其它代码都不要使用,原因是容易误用。可以使用std::move,但不要使用std::forward。
- 慎重使用无符号整数,因为整数强转为无符号时,往往会带来很多bug。
- 代码注释必须使用英文,GIT提交时的注释推荐用英文(但不强求)。
- 打开VS的空格可见性设置,在写代码时才能注意到有没有使用tab缩进。VS菜单Edit/Advanced/View White Space
设置缩进的方法:
设置显示空格的方法:
成长,就是一个不动声色的过程,一个人熬过一些苦,才能无所不能。