C++命名规范 原创 C++开发 2022年3月27日 22:55 夏至未至 1174 当前内容 13739 字,在路上,马上到,马上到 ## 命名规则 ### 类的命名 类通常是名词;类可以加后缀,代理类(Agents),加上后缀是DownloadAgents就更好了。类名不需要告知,是从哪里继承而来。用大写字母作为单词的分隔,每个单词的首字母大写,其他小写。名字的第一个字母应该大写。不需要含下划线,例如: class CreateOneTwo class Name ### 类库的,或者程序库的命名 使用命名空间防止名字冲突。如果编译器没有实现命名空间,需要用前缀来避免名字冲突,不过前缀不要长,2个字母是最好的,例如: JSON 是库,需要用它完成一个数据结构的库,为标识,就需要一个前缀: class JsLinkList { // something } ### 方法和函数的命名 与类的命名规则相同, 例如: class CreateOneTwo { public: int Dolt(); void HandleErrorMsg(); } 需要清楚的告知这函数是做什么的;例如: DumpDataToFile() 函数名总是以动词开头,后边跟其名词;加上必要的后缀(名词后缀),例如: Max:表示取最大值; RetryMax:表示可以接受的最大数 Cnt:表示当前的计数值 Key:表示键值 必要的前缀也很重要(一般动词),例如: Is:用于询问一些问题:以Is开头,即就是表示一个查询 Get:用于获取一个值 Set:用于设置一个值 IsHitRetryLimit:函数名表示:是否在可接受的范围 ### 类属性的命名 属性,`通常是非公有的数据成员`,名字以字母 `m`头;在 `m或者m_`后面,使用与类名相同的规则;`m或或者m_`,总是位于其他的修饰符(`比如表示指针的 p`)的前面;例如: class CreateBigData { public: int VarAbc(); private: int mVarAbc; int mErrorNumber; String* mpName; String* m_pName; } ### 函数参数的命名 第一个字母必须小写;第一个字母之后的字母单词使用与类名相同的规则;例如: class CreateBigData { public: // 第一个字母小写,这是方法参数 int StartYourEnginess(Engine& rSomeEngine, Engine& rAnotherEngine); } ### 局部变量的命名 所有字母都是小写;使用下划线作为单词的分隔,例如: int CreateBigData::HandleErrorMsg(int errorNumber) { int error = OsErr(); //局部变量全部小写,单词间用下划线隔开 Time time_of_error; ErrorProcessor error_processor; } ### 指针变量的命名前缀 指针变量,多数情况下,前边应该加油字母 `p`,`*`号应该靠近类型,而不应该是变量,尽管都可以(最好是靠近类型),例如: String* pName = new String; 注意: String* pName,name;是这样的 String* pName; // 这里不符合指针命名规范; String* name; ### 引用变量和返回引用函数的命名前缀 引用必须用 `r` 作为前缀,例如: class Test { public: //参数是引用变量 void DoSomething(Statusinfo& rStatus); //返回引用的函数,命名前缀是一个r; Statusinfo& rStatus(); // 返回常量引用,所以,不符合这个规则 constStatusinfo& Statu() const; private: //既是类的私有成员,又是引用变量,所以前缀是'm+r'; Statusinfo& mrStatus; } ### 全局变量的命名前缀 全局变量的命名,总是以`g`或`g_`作为前缀;例如: Logger g_Log; //全局指针变量 Logger* g_pLog; ### 全局常量的命名 全局常量全部大写,并以 `_` 下划线作为分隔;例如: const int A_GLOBOAL_CONSTANT = 5; ### 静态变量的命名规则 静态变量以 `s` 作为前缀;例如: class Test { private: //既是类的私有成员,静态变量,所以前缀是'm+s' static Statusinfo m_sStatus; } ### 自定义类型(typedef)的命名 类型名称指的是用 `typedef` 定义的名称;类型定义名称使用`和类名相同的规则,并用Type作为后缀`;例如: typedef unit16 ModuleType; typedef unit32 SystemType; ### 宏定义的命名 所有单词的字母都必须大写,并用下划线做分隔。例如: #define MAX(a,b) blah #define IS_ERR(err) blah ### C函数的命名 C++项目中尽量少用C函数;C函数使用GNU规范,所有字母都是用小写,并用下划线做为单词的分隔;例如: int some_bloody_function() { // DoSomethings(); } 特别的,为了兼容C/C++,在必要的时候,在C++中应该以下面的格式定义C函数; extern "C" int some_bloody_function(); 或者推荐在C/C++使用如下的格式: #ifdef __cplusplus__ extern "C" { #endif int some_bloody_function() { // DoSomethings(); } #ifdef __cplusplus__ } #endif ### 枚举的命名 所有字母都是大写,并用下划线作为单词分隔。例如: enum PinStateType { PIN_OFF, PIN_ON }; enum { START_ERR, START_OPEN, START_RUNNING, START_DYING }; ### 含有度量单位的名称: 表示时间:mTimeoutMsecs 表示重量:mMyWeightLbs 表示其他度量单位:把度量单位添加到名称当中,以便确认 ### 缩写名称不可全部用大写: 缩写总是以一个大写字母开头,后边全部是小写,例如: class NetworkAbcKey 按规则:绝对不是:NetworkABCKey; ## 排版规则 ### 布局和模板 #### 类的布局模板 使用下面的模板来创建一个新的类: /** * 用一行来描述类 * *#include "AAAAA.h" *-llib * * 类的详细说明 * * @seesomething */ 模拟类头文件: #ifndef SORUTION_PROJECT_CLASSNAME_H #define SORUTION_PROJECT_CLASSNAME_H // 在这里包含系统头文件 // // 在这里包含项目头文件 // // 在这里包含局部头文件 // // 在这里放置前置引用 // class AAA { public: // 类的生命周期控制函数,如构造和析构,以及状态机 /** *Default constructor. */ AAA(void); /** *Copy constructor. * *@param from The value to copy to this object. */ AAA(const AAA& from); /** *Destructor. */ virtual ~AAA(void); // 在这里放置类的运算操作符 /** *Assignment operator. * *@param from THe value to assign to this object. * *@return A reference to this object. */ AAA& operator=(AAA& from); // 在这里放置类的操作 // 在这里放置属性存取 // 在这里放置类的状态查询 protected: // 保护 private: // 私有 }; // 内联方法定义(需在头文件中定义) // // 外部引用 // #endif // SORUTION_PROJECT_CLASSNAME_H 定义的顺序是: public, protected, private,要清楚public/protected/private都应该放置哪些东西 #### 源文件格式 #include "AAA.h" /////////////// PUBLIC/////////////////////// //================= 构造函数 ==================== XX::XX() { }// XX XX::XX(const XX&) { }// XX XX::~XX() { }// ~XX //=============== 操作符========================= XX& XX::operator=(XX&); { return *this; } //==============类的操作 ===================== //==============属性存取 ===================== //==============状态查询 ===================== ///////////// PROTECTED ////////////////// ///////////// PRIVATE ////////////////// #### 保护头文件不被重复包含 应使用宏定义来保护头文件不被重复包含: #ifndef SORUTION_PROJECT_CLASSNAME_H #define SORUTION_PROJECT_CLASSNAME_H #endif // SORUTION_PROJECT_CLASSNAME_H 如果使用命名空间的时候,要把命名空间加到文件名前面: #ifndef SORUTION_PROJECT_NAMESPACE_CLASSNAME_H #define SORUTION_PROJECT_NAMESPACE_CLASSNAME_H #endif #### 方法和函数的布局 对于有较多参数的函数的写法,如果参数较多,一行写不下,我们应该分成几行来写,并且每个参数都另起一行对齐: int AnyMethod( int arg1, int arg2, int arg3, int arg4); 或 int AnyMethod( int arg1 ,int arg2 ,int arg3 ,int arg4); ### 缩进、制表符以及空格 缩进的时候,每一层缩进3,4,或8个空格。(推荐使用4个空格。不要使用TAB,用空格,大多数编辑器可以用空格代替TAB。TAB应固定4个空格,因为大多数编辑器都是这么设置的。虽然没有规定缩进的层次,但是4至5层是合适的。如果缩进的层次太多,你可能需要考虑是否进行代码重构了。例如: void func() { if (something bad) { if (another thing bad) { while (more input) { // dosomething } } } } ### 尽量使一行不要超过78个字母 有许多编辑器屏幕只有78个字母宽 ### 保证一行只写一条语句 一行最多只写一条语句,一行只定义一个变量,例如: 不要象下面这样: char** a, *x; int width, height; 要象这样: char** a= 0; // 文档说明 char* x= 0; // 文档说明 ### 花括号规则 #### 花括号的位置 在关键字的下一行单独放置{}括号,并且与关键字对齐;或者在关键字后空一格开始第一个花括号,例如: if (condition) { // Do } while (condition) { // Do } #### 什么时候应使用花括号 所有的 if, while 和 do 语句,要么用单行格式,要么使用花括号格式。使用花括号格式: if (1 == somevalue) { somevalue = 2; } 或 if (1 == somevalue) { somevalue = 2; } 单行格式: if (1 == somevalue) somevalue = 2; #### 在花括号结束的位置加上注释 在花括号结束的位置加上注释是一个好习惯。假如前后花括号距离很远,注释就能帮你理解它是如何对应的。例如: while(1) { if (valid) { } // if valid else { } // not valid } // end forever #### 注意屏幕大小 一个语句块尽量不超过一个屏幕大小,这样,不要卷动屏幕就可以阅读代码。 ### 圆括号规则 圆括号与关键字之间应放一个空格。圆括号与函数名之间不要有空格。Return 语句不要使用圆括号,例如: if (condition) { // Do } for (int i = 0; i < 10; i++) { // Do } while (condition) { // Do } strcpy(s, s1); //函数名和()之间不需要空格 return 1; //return语句不要使用() ### ifelse语句的格式 if (条件) // 注释 { // Do } else if (条件) // 注释 { // Do } else // 注释 { // Do } 条件格式,总是把常量放在等号或不等于号的左边: if ( 6 == errorNum ) ... 一个很重要的理由是,假如漏写一个等号,这种写法会产生一个编译错误,有助于马上发现问题。比如: if ( errorNum == 6) ... 错写成: if ( errorNum = 6) ... // 这是一个不容易发现的灾难 ### switch格式 直通的case语句,应该放置一条注释说明这个case语句是直通到下一个case语句的。总是要写default语句,不管是否是需要。在case中需要定义变量的时候,应把所有代码放在语句块中。例如: switch (...) { case 1: ... // 继续执行case2 case 2: { int v; //变量最好是在使用的时候在定义,避免太早定义: ... } break; default: } ### 使用goto,continue,break 和 ?: (三目元算符) #### goto 尽量避免使用 `goto` 语句。一个合理使用 `goto` 语句的场合是,当你需要从多层循环中跳出。例如: for (...) { while (...) { ... if (disaster) goto error; //跳出循环 } } ... error: //跳转的标号必须单独在一行的最左边。Goto语句需要有相应的注释,说明它的用途。 clean up the mess #### Continue and Break Continue 和break 实际上起到与goto一样的作用,因此,尽量少用为上。并且,Continue与break最好不要连用。 #### ?: 用括号把条件表达式括起来。不要在 ? : 中写上过多的代码,操作表达式应尽可能简洁。操作语句应分行写,除非它们能够简洁的放在一行当中。例如: (condition) ? funct1() : func2(); 或 (condition) ? longstatement : anotherlong statement; ### 运算符号的规则 一元操作符如(!、~ 等等)应贴近操作对象。例如: if (!IsOk) //!贴近对象 return ++v; 二元操作符如`(+、*、%、== 等等)`应在前后留空格。如: if ( v1 == v2) //二元操作符前后留空格 return v1 * 3; ++ 和 -- 尽量使用前置运算。在C++中,不管 ++i 还是 i++,总是++i更容易生成优化代码。如: for(int i = 0; i < 10; ++i) //************************特别注意,后置加加会有临时变量生成,所以前置加加最优化 ### 变量声明语句块 变量应该是随用随声明,不要集中在函数前(有些C语言不支持,则不在此要求之列)。特别是在for语句的循环变量,应只在for语句中定义。例如: for(int i = 0; i < 10; ++i) 声明语句块必须要对齐,类型,变量,等号和初始化值要分别对齐。例如: DWORD mDword; DWORD* mpDword; char* mpChar; char mChar; mDword = 0; mpDword = NULL; mpChar = NULL; mChar = 0; ## 文档及注释 应当使用文档自动生成工具,来生成相关的程序文档。 ### 文件或程序库的文档注释 可以为整个文件编写文档。例如: /** @file file.h * Abrief file description. * Amore elaborated file description. */ ### 类文档注释 在类定义前面应加上类说明文档。例如: /** WindowsNT * @brief Windows Nice Try. * @author Bill Gates * @author Several species of small furryanimals gathered together * in a cave and grooving with a pict. * @version 4.0 * @date 1996-1998 * @bug It crashes a lot and requires hugeamounts of memory. * @bug The class introduces the more bugs, thelonger it is used. * @warning This class may explode in your face. * @warning If you inherit anything from thisclass, you're doomed. */ class WindowsNT {}; //这里是类 ### 函数文档注释 函数注释,所有的参数都应该有文档说明(param),所有的返回代码都应该有文档说明(return),所有的例外都应该有文档说明(exception)。可以使用(see)引用有关的开发资源。例如: /** * 赋值操作符 * *@param val 将要赋给本对象的值 * *@return 本对象的引用 */ XX& operator =(XX& val); 注释属性:一些自动文档工具定义的属性可以包含在文档中,常用的有: - 前提条件 (pre):定义调用这个函数的前提条件 - 警告说明 (warning):定义一些关于这个函数必须知道的事情。 - 备注说明 (remarks):定义一些关于这个函数的备注信息。 - 将要完成的工作 (todo):说明哪些事情将在不久以后完成 - 使用例子说明 (example):一个图片能表达100句话,一个好的例子能解答1000个问题。 例如: /** * 复制一个字串 * *@pre * - 需要保证(from != 0) * - 需要保证(to != 0) * *@warning * 缓冲区必需足够大,以便容纳的下要拷贝的字串。 * *@example teststrcpy.cpp * *@param from 要拷贝的字串 *@param to 用于容纳字串的缓冲区 *@return void */ void strcpy(constchar* from, char* to); ### include语句注释 如果有必要,`#include` 语句也应有注释,它可以告诉我们,为什么要包含这个头文件。(是如果有必要的话) ### 语句块注释 语句块的注释可以用在语句块的开头和结束位置: { // Block1 (meaningful comment about Block1) ... some code { // Block2 (meaningful comment about Block2) ... somecode } // End Block2 } // End Block1 ## 编码要求 ### 不要忽略编译器的警告 编译器的警告,通常能够指示出编码存在的笔误或逻辑错误。因此,不能轻视编译器的任何警告。正确的作法是,不允许代码在编译时产生任何警告信息。 ### 应使用源代码管理器 根据开发规模,选择合适的源代码管理器。使用源代码管理器是非常必要的。(git) ### 固有的类方法成员 - 默认构造函数(DefaultConstructor):如果构造函数的所有参数都是可选的,那么这个构造函数也是默认构造函数。如果没有定义任何普通构造函数,则编译将自动生成一个。 - 虚析构函数(Virtual Destructor):如果一个类可以被继承,那么应该使用虚析构函数。如果没有定义虚析构函数,则编译器将自动生成一个。 - 拷贝构造函数(Copy Constructor):如果一个类不应该被拷贝,应该定义一个私有的拷贝构造函数,并且不定义它的实现。如果不知道一个类是否应该被拷贝,就认为它是不可拷贝的,直到你确认它应该被拷贝。如果没有定义拷贝构造函数,则编译器将自动生成一个。 - 赋值操作(AssignmentOperator):如果一个类不应该被赋值,应该定义一个私有的赋值操作函数,并且不定义它的实现。如果不知道一个类是否应该被赋值,就认为它是不可赋值的,直到你确认它应该被赋值。如果没有定义赋值操作函数,则编译器将自动生成一个。 ### 使用命名空间 命名规则:根名字一般是设计者的名字。比如公司名称等等。不要在全局空间使用using语句。 ### 初始化所有的变量 无论如何,都要初始化所有的变量。我们无法保证编译器会给个什么样的初值。 ### 保持函数短小精悍 一般情况下,一个函数最好在一个屏幕内,不要超过三个屏幕。 ### 对空语句进行注释 For和while语句如果跟随一个空语句,需要对此语句进行注释,并且空语句应另起一行。如: while(*dest++ = *src++) ; // VOID 不允许写成: while (*dest++ = *src++) ; ### 不要用if语句的默认方法测试非零值 If语句只用于检测布尔值(bool),不要用默认的方法测试非零值,比如: 建议使用: if (FAIL != f()) 不建议使用下面的表达式: if (f()) 宏定义的情况也一样: #define STREQ(a,b) (strcmp((a), (b)) == 0) 或者使用内联函数: inline bool StringEqual(char* a, char* b) { (strcmp(a, b)== 0) ? return true : return false; Or more compactly: returnstrcmp(a, b) == 0; } ### 布尔类型 早期的C++没有布尔类型,但新的C++标准增加了布尔类型。如果可以使用内置的布尔类型的情况下,应使用布尔类型。早期的布尔类型定义为: typedef int bool; #defineTRUE 1 #defineFALSE 0 或: const intTRUE = 1; const int FALSE= 0; 在这种情况下,条件表达式不要比较1值(如TRUE,YES等等),而要用0值(如FALSE,NO等等)进行比较。因为多数函数返回0表示FALSE,而非零表示TRUE。如: if (TRUE ==func()) { ... // 错误:假如func()返回 2 怎么办? 必须写成: if (FALSE !=func()) { ... ### 避免在语句中内含赋值 只有一种情况可以在语句中内含赋值,它要能使代码显得更易理解,例如: while (EOF != (c= getchar())) { process thecharacter } ++ 和 -- 操作也是一种赋值语句,内含赋值语句常常会带来一些副作用。在遇到这种情况时,我们应分成几个语句来写。比如: a = b + c; d = a + r; 不应该写成: d = (a = b + c)+ r; ### 正确的使用const C/C++ 提供const 关键字,用于指示不应该被修改的对象或数据。正确的使用Const既可以提供编译器的优化指示,也能够避免一些编码错误。 ### 不要在头文件定义数据 不要把数据定义放在头文件,例如: /* * aheader.h */ int x = 0; ### 不要直接使用数字 直接使用数字,会使源代码难以理解和维护。如: if (22 ==foo) { start_thermo_nuclear_war(); } else if (19 == foo) {refund_lotso_money(); } else if (16 == foo) {infinite_loop(); } else { cry_cause_im_lost(); } 当一段时间过去以后,有谁会记得22和19是什么意思?假如数字改变,或者是编写错误,更是难以发现问题。我们可以用#define或者常量来改变这一状况,例如: #define PRESIDENT_WENT_CRAZY (22) const int WE_GOOFED= 19; enum { THEY_DIDNT_PAY=16 }; if (PRESIDENT_WENT_CRAZY == foo) { start_thermo_nuclear_war(); } else if (WE_GOOFED == foo) {refund_lotso_money(); } else if (THEY_DIDNT_PAY == foo) { infinite_loop();} else {happy_days_i_know_why_im_here(); } ### 宏 如果可以,使用内联函数代替宏。例如: #ifndef MAX #define MAX(x,y) (((x) > (y) ? (x) : (y)) // 取最大数 #endif 使用内联函数可以达到相同的效果,而且更安全: inline int max(int x, inty) { return (x> y ? x : y); } 要注意副作用,必须小心副作用,因为在调用表达式时,会发生潜在的错误。例如: MAX(f(x),z++); 表达式总是用括号括起来,在宏展开时,使用括号可以避免宏展开后产生的二义性。例如: #define ADD(x,y) x + y 必须写成: #define ADD(x,y) ((x) + (y)) 保证宏名称的唯一性,和全局变量一样,宏也会与其它名称产生冲突。下面两条规则有助于解决这个问题。在宏名称前加上库的名字,避免使用简单而常用的名字,如: MAX 和 MIN 本文标题: C++命名规范 本文作者: 夏至未至 发布时间: 2022年3月27日 22:55 最近更新: 2022年3月27日 23:08 原文链接: 许可协议: 署名-非商业性-禁止演绎 4.0 国际(CC BY-NC-ND 4.0) 请按协议转载并保留原文链接及作者 C++开发(12) 命名规范(1) 上一个 C++串口通信同、异步读写详解 下一个 学生通讯录系统 当前文章评论暂未开放,请移步至留言处留言。