当前位置: 首页 > 科技观察

最全面的C-C++编码规范总结

时间:2023-03-16 23:06:55 科技观察

最全面的C/C++编码标准总结良好的可读性和可维护性。例如,我们可以规定某个项目的C语言程序必须遵循这样的规定:变量命名、头文件编写和#include等。下面是一些被广泛采用的编码标准:GNUCodingStandardsGuidelinesfortheUsetheCLanguageinVehicleBasedSoftwareC++CodingGuidelinesSUNCodeConventionsforJava以下是一些介绍编码和编码标准的书籍:C++CodingStandards,ChenShizhong,People'sPostsandTelecommunicationsPress,2002GuidelinesforHigh-qualityProgramming:C++/CLanguage,LinRuietal.,ElectronicsIndustryPress,2003注:以下仅为课题组经验总结,并不适用于所有场景。对于高质量的项目,一般做到:代码简洁精炼、美观、可读性好、效率高、复用性高、可移植性好、高内聚、低耦合、无冗余,不满足这些原则,必须指定.标准化,代码有章可循。必须指定特殊排版、特殊语法和特殊说明。一、文件排版1、包含头文件1.1先是系统头文件,再是用户头文件。1.2系统头文件和稳定目录结构应采用包含子路径的方式。1.3自定义头文件,目录结构不稳定,应在dsp中指定include路径。1.4系统头文件的应用:#include1.5同一文件的自定义应用:#include"xxx.h"1.6只引用需要的头文件。2.h和cpp文件2.1头文件命名为*.h,内联文件命名为*.inl;C++文件名为*.cpp2.2文件名大小写混合。例如DiyMainview.cpp、infoview.cpp。不要使用无意义的名称:例如XImage.cpp;SView.cpp;xlog.cpp;2.3头文件除特殊情况外,应使用#ifdef控制块。2.4头文件#endif应该使用行尾注释。2.5头文件,首先包含代码块,其次是宏定义代码块,然后是全局变量、全局常量、类型定义、类定义、内联部分。2.6CPP文件,包括指令、宏定义、全局变量、函数定义。3.文件结构3.1文件应包含标题注释和内容。3.2函数体和类体之间原则上使用2个空行,特殊情况可以使用1个或不使用空行。4、空行4.1在文件头、控制块、#include部分、宏定义部分、类部分、全局常量部分、全局变量部分、函数与函数之间,使用两个空行。2.注释1.文件头注释1.1作者、文件名、文件描述、生成日期(可选)2.函数注释2.1注释必须写在关键函数上,说明函数的用途。2.2对于特殊功能参数,需要说明参数的用途,谁负责释放等。2.3除特殊情况外,注释应写在代码之前,不能写在代码行之后。2.4为每个#else或#endif提供行尾注释。2.5关键代码注释,包括但不限于:赋值、函数调用、表达式、分支等。2.6未实现的完整代码,或需要进一步优化的代码,应添加//TODO...2.7调试代码,添加注释//onlyforDEBUG2.8需要注意的代码,添加注释//NOTE...2.9对于较大代码块的结尾,比如for、while、do等,可以add//endfor|while|do三、命名1、原则1.1同一性:在编写子模块或派生类时,在创建类时,遵循其基类或模块整体的命名风格,在类中保持相同的命名风格整个模块。1.2标识符构成:标识符采用英文单词或其组合,应直观易拼,能从文中理解。用词要准确,避免拼音。1.3最小化长度&&最大化信息量原则:在保持标识符含义清晰的同时,尽可能缩短其长度。1.4避免过于相似:不要出现类似的仅以大小写区分的标识符,如“i”与“I”、“function”与“Function”等。1.5避免不同级别作用域的名称重名:不要程序中有同名的局部变量和全局变量。虽然两者的范围不同,不会出现语法错误,但很容易产生误解。1.6正确命名具有互斥含义的标识符:使用正确的反义词命名具有互斥含义的标识符,如:“nMinValue”和“nMaxValue”、“GetName()”和“SetName()”……1.7避免数字出现在名称:尽量避免名称中出现数字,如Value1、Value2等,除非逻辑上确实需要数字。这是为了防止程序员偷懒,不肯动脑筋命名,造成无意义的名字(因为数字最方便)。2、T类、C类、M类、R类2.1T类代表简单数据类型,对资源没有控制权,在析构过程中不释放资源。2.2C表示继承自CBase的类。这个类不能从栈中定义变量,只能从堆中定义。2.3M代表接口类。2.4R是资源类,通常是系统的固有类型。除特殊情况外,R类型不应出现在开发代码中。3、函数名3.1M类函数名应以HandleXXX命名,例如:HandleTimerEvent;java风格不推荐,比如handleTimerEvent;除了标准的c风格代码外,不建议使用下划线,例如handle_event。3.2Leave函数,后缀为L。3.3Leave函数,入栈和清栈,使用后缀LC。3.4离开函数,删除对象,使用后缀LD。4.函数参数4.1函数参数以a为前缀。4.2避免与匈牙利语混合的命名规则,例如apBuffer名称。只需使用一个缓冲区。4.3当函数参数较多时,应考虑改用结构体。4.4如果不可避免的函数参数较多,在排版时应考虑每个参数占一行,参数名垂直对齐。5.成员变量5.1成员变量以m为前缀。5.2避免与匈牙利语混合的命名规则,例如mpBuffer名称。只需使用mBuffer。6.局部变量6.1循环变量和简单变量可以使用简单的小写字符串。例如,inti;6.2指针变量以p开头,如void*pBuffer;7.全局变量7.1全局变量以g_为前缀。8.类名8.1类名和对象名应为名词。8.2实现行为的类成员函数的名称应该是动词。8.3类的访问和查询成员函数名应为名词或形容词。9.风格兼容性9.1对于移植或开源代码,可以使用原始风格代替C++命名约定。4、在代码风格方面1、制表符和空格1.1每行开头的缩进只能使用制表符,不能使用空格,输入内容后统一使用空格。除了最开始的缩进控制使用Tab,其他部分都需要用空格缩进对齐。这避免了在不同编辑器下的显示错位。1.2代码行末尾不应有多余的空格。1.3“::”、“->”、“.”前后不要加空格。1.4“,”,“;”前不要加空格。2、类型定义和{2.1类、结构、枚举、联合:用花括号换行3、函数3.1函数体中的{需要换行,{前不能缩进。3.2除特殊情况外,函数体中不能出现两个空行。3.3除特殊情况外,函数体中不能使用宏定义指令。3.4在一个函数体中,逻辑上密切相关的语句之间不应有空行,其他地方应加空行分隔。3.5对于头文件中定义的内联函数,函数之间不需要使用空行,建议使用空行。4、代码块4.1“if”、“for”、“while”、“do”、“try”、“catch”等语句各自占一行,执行语句不可紧跟。不管执行语句有多少,都必须加上“{}”。这可以防止在编写和修改代码时出错。4.2“if”、“for”、“while”、“do”、“try”、“catch”的括号和表达式,括号可以放在关键字的旁边,这样可以强调表达式。5.else5.1如果if语句中有else语句,用}else{写成一行。不建议使用3行代码。6.代码行6.1一行代码只做一件事,比如只定义一个变量,或者只写一条语句。这样的代码易于阅读,也易于编写注释。6.2多行变量定义,为了追求代码布局美观,变量可以垂直对齐。6.3代码行的长度应控制在一定的字符数以内,最好能在当前屏幕上全部看到。7.switch语句7.1case关键字要和switch对齐。7.2如果case子语句中有变量,应该用{}括起来。7.3如果有平行的类似简单case语句,考虑将case代码块写成单行代码。7.4简单案例之间无需使用空行,复杂案例应考虑使用空行分隔。7.5caseword语句的花括号另起一行,不要和case写在同一行。7.6为所有switch语句提供了默认分支。7.7如果一个案例不需要休息,则必须声明并附上评论。8、循环8.1空循环可以使用for(;;)或while(1)或while(true)9、类9.1类继承应采用每个基类占一行的方式。9.2单继承允许基类和类定义放在同一行。如果使用多行,则应用Tab缩进。9.3多重继承当基类较多时,应将基类分成几行,并用Tab缩进对齐。9.4重载基类的虚函数,在虚函数组前写注释//implementXXX9.5Frienddeclarations应该放在类的末尾。10.宏10.1不要以分号结束宏定义。10.2功能宏的每个参数都必须包含在内。10.3没有参数的宏函数也应该定义为函数。11.goto11.1尽量不要使用goto。5.类型定义指针和引用时,*和&跟在类型后面。除非必须,否则尽量避免使用浮点数。使用typedef来简化程序中的复杂语法。避免定义未命名的类型。例如:typedefenum{EIdle,EActive}TState;很少用union,如果一定要用,就用简单的数据类型成员。用枚举替换(一组相关的)常量。不要使用幻数。尝试使用引用而不是指针。定义变量后立即初始化,不要等到使用时才初始化。如果有更优雅的解决方案,请不要使用强制转换。6.表达式避免在表达式中使用赋值语句。避免对浮点类型进行相等或不相等的判断。不能对枚举类型进行操作,然后将其赋值给枚举变量。不要在循环过程中修改循环计数器。要检测空指针,请使用if(p)检测非空指针,请使用if(!p)VII.函数1.引用1.1引用类型作为返回值:函数必须返回一个存在的对象。1.2引用类型作为参数:调用者必须传递一个存在的对象。2、常量成员函数2.1表示该函数只读取对象的内容,不修改对象。3、返回值3.1除void函数、构造函数、析构函数外,其他函数必须有返回值。3.2当函数返回引用或指针时,用文字描述其有效期。4.内联函数4.1内联函数应该将函数体放在类之外。4.2只有简单的函数才需要设计成内联函数,业务逻辑复杂的函数不应该这样做。4.3不要把虚函数设计成内联函数。5.函数参数5.1只读取参数的内容,不要修改其内容,使用常量引用。5.2修改参数内容,或需要通过参数返回,使用非常量应用。5.3简单数据类型按值传递。5.4复杂数据类型使用引用或指针。八、类1.构造函数1.1构造函数的初始化列表要与类的顺序一致。1.2初始化列表中的每一项都应该单独一行。1.3避免使用一个成员来初始化另一个成员。1.4构造函数应初始化所有成员,尤其是指针。1.5不要在构造函数和析构函数中抛出异常。2、纯虚函数2.1M类的虚函数应该设计成纯虚函数。3.构造和析构函数3.1如果类可以被继承,类的析构函数应该设计成虚函数。3.2如果类不允许继承,类的析构函数应该设计成非虚函数。3.3如果类不能被复制,复制构造函数和赋值运算符应该设计成private。3.4如果为类设计了构造函数,就应该有析构函数。4.成员变量4.1尽量避免使用mutable和Volatile。4.2尽量避免使用公共成员变量。5.成员函数5.1力求类的接口少而全。5.2尽量使用常量成员函数代替非成员函数,const函数5.3除非有特殊原因,永远不要重新定义(继承)非虚函数。(这是覆盖,基类的一些属性没有初始化)6、继承6.1继承必须满足IS-A的关系,HAS-A要采用包含。6.2虚函数不要使用默认参数。6.3除非特别要求,否则应避免大而全的虚函数,虚函数的功能应单一。6.4除非特别要求,避免将基类转换为派生类。7.Friends7.1尽量避免使用友元函数和友元类。九、错误处理用new操作符申请内存。要释放内存,请使用删除运算符。new和delete,new[]和delete[]是成对使用的。内存申请完成后,需要检测指针申请是否成功,申请失败的处理。谁申请谁就会被释放。优先级:函数级、类级、模块级。释放内存后,将指针清空,避免出现野指针。在使用指针前判断合法性时,需要考虑空case的处理。使用数组时,首先要判断索引的合法性,索引无效的情况下处理。代码不得有编译警告。使用错误传递的错误处理想法。守卫风格:首先处理所有错误情况,然后再处理正常情况。嵌套do-while(0)宏:目的是将一组语句变成一条语句,避免被其他if等打断。10.性能使用前向声明代替#include指令。M类;尝试使用++i而不是i++。也就是说,使用前缀而不是后缀。尽量把计算求值表达式写在for循环之前。尽量避免在循环体内定义对象。避免对象复制,尤其是昂贵的对象复制。避免生成临时对象,尤其是大型对象。当心大型对象数组。80-20原则。11.兼容性符合ANSIC和ISOC++国际标准。确保类型转换不会丢失信息。注意与双字节字符的兼容性。注意运算溢出问题。不要假设类型的存储大小。不要假设表达式的操作顺序。不要对函数参数的求值顺序做出假设。不要假设不同源文件中静态或全局变量的初始化顺序。不要依赖基于编译器的、未指定或未定义的功能。将所有#included文件名视为区分大小写。避免使用全局变量、静态变量、函数静态变量、类静态变量。在使用静态库、动态库、多线程环境时,会造成兼容性问题。不要重新实现标准库函数,比如那些已经存在于STL中的函数。