• C++编程功底和常用规则



    前言

    本文章不建议C++小白阅读,否则你无法理解其中的一些精髓,一些属于C++独有的语法知识。掌握代码规范在日常开发中尤为重要,这是一个软件程序员的基本素养!

    一、C++代码规范介绍

    1、代码规范的目标准则

    (1)代码简洁精炼,美观,可读性好;

    (2)高效率;
      如深拷贝与浅拷贝,C++拷贝函数(传值和传引用),++i 比 i++效率高(i++在运算过程中使用了临时变量)

    (3)高复用,可移植性好;
      可以跨平台/操作系统

    (4)高内聚,低耦合;

    (5)没有冗余;
      代码注释简洁易懂,无多余废话。代码无多余无用语句,逻辑清晰简洁。

    (6)规范性,代码有规可循;
      便于他人阅读理解和使用,一般一个大项目的开发都是由几个人共同完成的,需要分工合作。

    (7)特殊排版、特殊语法、特殊指令,必须用注释特别说明;

    2、代码规范的参考书

    (1)C++编码规范,陈世忠,人民邮电出版社,2002

    (2)高质量程序设计指南:C++/C语言,林锐等,电子工业出版社,2003

    3、代码规范标准参考

    (1)google、华为等大厂

      C/C++ 编码规范

    (2)典型开源项目,如opencv、opengl、boost库等

    Summary:不同厂商,不同开源项目之间可能会有差异,二者有些代码规范恰恰相反,如缩进是使用空格还是使用TAB键。

    二、常见代码规范总结

    1、头文件包含

    (1)先系统头文件,后用户头文件。系统头文件使用:#include <xxx.h>,自定义头文件
    使用:#include "xxx.h"
    
    (2)如果头文件与程序文件在同一个子目录里,则可以这么写:#include "./xxx.h" 
    
    (3)如果头文件在上级子目录,则可以这么写:#include "../xxx.h"
    
    (4)如果头文件位于某个下级子目录里,那么以下级子目录的名字开头:
    #include "somedir/xxx.h"
    
    (5)如果头文件位于某个与当前子目录平行的“兄弟”子目录里,则需要这么写:
    #include "../somedir/xxx.h"
    
    (6)只引用需要的头文件
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2、cpp和h文件常规规则

    (1)文件名常用大小写混合,或者小写混合。例如DiyMainview.cpp,infoview.cpp

    (2)头文件除了特殊情况,应使用#ifndef控制块

    #ifndef ADD_H
    #define ADD_H
    	//执行定义
    #endif		// ADD_H
    
    • 1
    • 2
    • 3
    • 4

    (3)头文件#endif应采用行尾注释

    (4)头文件中的内容顺序:包含代码块(#include 语句),宏定义代码块,全局变量,
    全局常量,类型定义,类定义,内联部分

    (5)Cpp文件中的内容顺序:包含指令,宏定义,全局变量,函数定义。

    3、注释方面

    (1)文件头注释:作者,文件名称,文件说明,日期和版本(可选),修改和维护说明(可选)
    (2)函数注释:关键函数必须写上注释,说明函数的用途。
    (3)函数注释:特别函数参数,需要说明参数的目的,由谁负责释放等等。
    (4)函数注释:除了特别情况,注释写在代码之前,不要放到代码行之后。
    (5)函数注释:对每个#else或#endif给出行末注释。
    (6)函数注释:关键代码注释,包括但不限于:赋值,函数调用,表达式,分支等等。
    (7)函数注释:尚未实现完整的代码,或者需要进一步优化的代码,应加上	// TODO ...
    (8)函数注释:调试的代码,加上注释	// only for DEBUG
    (9)函数注释:需要引起关注的代码,加上注释	// NOTE ...
    (10)函数注释:对于较大的代码块结尾,如for, while, do等,可加上
    // end for|while|do
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    4、命名

    (1)同一性:在编写一个子模块或派生类的时候,要遵循其基类或整体模块的命名风格,保持命名风格在整个模块中的同一性。

    (2)标识符组成:标识符采用英文单词或其组合,应当直观且可以拼读,可望文知意,用词应当准确,避免用拼音命名。

    (3)最小化长度 && 最大化信息量原则:在保持一个标识符意思明确的同时,应当尽量缩短其长度。

    (4)避免过于相似:不要出现仅靠大小写区分的相似的标识符,例如"i"与"I","function"与"Function"等等。

    (5)避免在不同级别的作用域中重名:程序中不要出现名字完全相同的局部变量和全局变量,尽管两者的作用域不同而不会发生语法错误,但容易使人误解。

    (6)正确命名具有互斥意义的标识符:用正确的反义词组命名具有互斥意义的标识符,如:“nMinValue"和"nMaxValue”,"GetName()“和"SetName()”

    (7)避免名字中出现数字编号:尽量避免名字中出现数字编号,如Value1,Value2等,除非逻辑上的确需要编号。这是为了防止偷懒,不肯为命名动脑筋而导致产生无意义的名字(因为用数字编号最省事)。

    5、TCMR类

    (1)T类表示简单数据类型,不对资源拥有控制权,在析构过程中没有释放资源动作
    (2)C表示从CBase继承的类。该类不能从栈上定义变量,只能从堆上创建
    (3)M表示接口类
    (4)R是资源类,通常是系统固有类型。除了特殊情况,不应在开发代码中出现R类型
    (5)M类的函数名称应采用HandleXXX命名,例如:HandleTimerEvent;不推荐采用
    java风格,例如handleTimerEvent;除了标准c风格代码,不推荐用下划线,例如,
    handle_event
    (6)M类的虚函数应设计为纯虚函数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    6、函数参数

    (1)函数参数用a作为前缀。
    (2)避免出现和匈牙利混合的命名规则如apBuffer名称。用aBuffer即可。
    (3)函数参数比较多时,应考虑用结构体代替。
    (4)如果不能避免函数参数比较多,应在排版上可考虑每个参数占用一行,参数名竖向对齐。
    
    • 1
    • 2
    • 3
    • 4

    7、成员变量、局部变量、全局变量

    (1)成员变量用m最为前缀。
    (2)避免出现和匈牙利混合的命名规则如mpBuffer名称。用mBuffer即可。
    
    (3)循环变量和简单变量采用简单小写字符串即可。例如,int i;
    (4)指针变量用p打头,例如void* pBuffer;
    
    (5)全局变量用g_最为前缀
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    8、类名、风格兼容性

    (1)一般类和对象名应是名词。
    (2)实现行为的类成员函数名应是动词。
    (3)类的存取和查询成员函数名应是名词或形容词。
    
    (4)对于移植的或者开源的代码,可以沿用原有风格,不用C++的命名规范。自己写的用自己的
    风格,这种情况下允许出现不同的风格。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    9、Tab、空格、大括号

    (1)每一行开始处的缩进只能用Tab,不能用空格,输入内容之后统一用空格。除了最开始的
    缩进控制用Tab,其他部分为了对齐,需要使用空格进行缩进。这样可以避免在不同的编辑器
    下显示不对齐的情况
    (2)在代码行的结尾部分不能出现多余的空格
    (3)不要在"::", "->", "."前后加空格
    (4)不要在",", ";"之前加空格。
    
    (5)类,结构,枚举,联合:大括号另起一行
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    10、函数

    (1)函数体的{需要新起一行,在{之前不能有缩进。
    (2)除了特别情况,函数体内不能出现两个空行。
    (3)除了特别情况,函数体内不能宏定义指令。
    (4)在一个函数体内,逻揖上密切相关的语句之间不加空行,其它地方应加空行分隔。
    (5)在头文件定义的inline函数,函数之间可以不用空行,推荐用一个空行。
    
    • 1
    • 2
    • 3
    • 4
    • 5

    11、代码块、代码行

    (1)"if""for""while""do""try""catch"等语句自占一行,执行语句不得紧跟
    其后。不论执行语句有多少都要加"{ }"。这样可以防止书写和修改代码时出现失误。
    
    (2)"if""for""while""do""try""catch"的括号和表达式,括号可紧挨关键字,
    这样强调的是表达式。
    
    (3)if语句如果有else语句,用 } else { 编写为一行,不推荐用3行代码的方式
    
    (4)一行代码只做一件事情,如只定义一个变量,或只写一条语句。这样的代码容易阅读,
    并且方便于写注释。
    (5)多行变量定义,为了追求代码排版美观,可将变量竖向对齐。
    (6)代码行最大长度宜控制在一定个字符以内,能在当前屏幕内全部可见为宜。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    12、switch语句、循环、goto

    (1)case关键字应和switch对齐。
    (2)case子语句如果有变量,应用{}包含起来。
    (3)如果有并列的类似的简单case语句,可考虑将case代码块写为一行代码。
    (4)简单的case之间可不用空行,复杂的case之间应考虑用空行分割开。
    (5)case子语句的大括号另起一行,不要和case写到一行。
    (6)为所有switch语句提供default分支。
    (7)若某个case不需要break一定要加注释声明。
    
    (8)空循环可用for( ; ; )或者while(1)或者while(true)
    
    (9)尽量不要用goto
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    13、类

    (1)多继承中,类继承应采用每个基类占据一行的方式
    (2)单继承可将基类放在类定义的同一行。如果用多行,则应用Tab缩进
    (3)多继承在基类比较多的情况下,应将基类分行,并采用Tab缩进对齐
    (4)重载基类虚函数,应在该组虚函数前写注释// implement XXX
    (5)友元声明放到类的末尾
    
    • 1
    • 2
    • 3
    • 4
    • 5

    14、宏

    (1)不要用分号结束宏定义
    (2)函数宏的每个参数都要括起来
    (3)不带参数的宏函数也要定义成函数形式
    
    • 1
    • 2
    • 3

    15、类型

    (1)定义指针和引用时*&紧跟类型 int& int* p;
    (2)尽量避免使用浮点数,除非必须
    (3)typedef简化程序中的复杂语法
    (4)避免定义无名称的类型。例如:typedef enum { EIdle, EActive } TState; 
    (5)少用union,如果一定要用,则采用简单数据类型成员
    (6)enum取代(一组相关的)常量
    (7)尽量不要使用MagicNumber(避免MagicNumber和使用的数据相同造成错误)
    (8)尽量用引用取代指针(引用在C++内部也是使用指针实现的)
    (9)定义变量完成后立即初始化,勿等到使用时才进行
    (10)如果有更优雅的解决方案,不要使用强制类型转换
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    16、表达式

    (1)避免在表达式中用赋值语句			if ((x == 4) || ((b = a) == 4))
    (2)避免对浮点类型做等于或不等于判断
    (3)不能将枚举类型进行运算后再赋给枚举变量
    (4)在循环过程中不要修改循环计数器
    (5)检测空指针,用if( p ) , 检测非空指针,用if( ! p )
    
    • 1
    • 2
    • 3
    • 4
    • 5

    17、函数和引用、常量成员函数、函数与返回值、内联函数、函数参数

    (1)引用类型作为返回值时,函数必须返回一个存在的对象(比如对象使用malloc申请的内存
    ,这样在函数返回时不会像在栈内存的局部变量一样被回收释放)
    (2)引用类型作为参数时,调用者必须传递一个存在的对象
    
    (3)常量成员函数:表示该函数只读取对象的内容,不会对对象进行修改
    
    (4)除开void函数,构造函数,析构函数,其它函数必须要有返回值
    (5)当函数返回引用或指针时,用文字描述其有效期(使人了解该内存何时被释放)
    
    (6)内联函数应将函数体放到类体外
    (7)只有简单的函数才有必要设计为内联函数,复杂业务逻辑的函数不要这么做
    (8)虚函数不要设计为内联函数
    
    (9)只读取该参数的内容,不对其内容做修改,用常量引用
    (10)修改参数内容,或需要通过参数返回,用非常量引用
    (11)简单数据类型用传值方式
    (12)复杂数据类型用引用或指针方式
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    18、类的构造函数、析构函数、成员变量

    (1)构造函数的初始化列表,应和类的顺序一致
    (2)初始化列表中的每个项,应独占一行
    (3)避免出现用一个成员初始化另一个成员
    (4)构造函数应初始化所有成员,尤其是指针
    (5)不要在构造函数和析构函数中抛出异常
    
    (6)如果类可以继承,则应将类析构函数设计为虚函数。
    (7)如果类不允许继承,则应将类析构函数设计为非虚函数。
    (8)如果类不能被复制,则应将拷贝构造函数和赋值运算符设计为私有的。
    (9)如果为类设计了构造函数,则应有析构函数。
    
    (10)成员变量尽量避免使用mutable和Volatile
    (11)尽量避免使用公有成员变量。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    19、类的成员函数、继承、友元函数

    (1)努力使类的接口少而完备。
    (2)尽量使用常成员函数代替非常成员函数
     https://www.cnblogs.com/hu983/p/5479099.html
    (3)除非特别理由,绝不要重新定义(继承来的)非虚函数。因为这样是覆盖,基类的某些属性
    无初始化
    
    (4)继承必须满足IS-A的关系,HAS-A应采用组合
    (5)虚函数不要采用默认参数,更不要在子类中override该虚函数时给新的默认值
    (6)除非特别需要,应避免设计大而全的虚函数,虚函数功能要单一
    (7)除非特别需要,避免将基类强制转换成派生类
    
    (8)尽量避免使用友元函数和友元类
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    20、错误处理

    (1)申请内存用new操作符,释放内存用delete操作符
    (2)new和delete,new[]和delete[]成对使用
    (3)申请内存完成之后,要检测指针是否申请成功,处理申请失败的情况
    (4)谁申请谁释放。
    (5)优先级:函数层面,类层面,模块层面
    (6)释放内存完成后将指针赋空,避免出现野指针
    (7)使用指针前进行判断合法性,应考虑到为空的情况的处理
    (8)使用数组时,应先判断索引的有效性,处理无效索引的情况
    (9)代码不能出现编译警告
    (10)使用错误传递的错误处理思想
    (11)畏惧风格:先处理所有可能发生错误的情况,再处理正常情况
    (12)嵌套do-while(0)宏:目的是将一组语句变成一个语句,避免被其他if等中断
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述

    21、性能

    (1)使用前向声明代替#include指令可提升编译期性能。但是Google C++风格指南中说:尽
    可能地避免使用前置声明。使用#include 包含需要的头文件即可
    
    (2)尽量用++i代替i++。即用前缀代替后缀运算。参考:
    https://blog.csdn.net/qq_36607894/article/details/106605140
    
    (3)尽量避免在循环体内部定义对象
    
    (4)避免对象拷贝,尤其是代价很高的对象拷贝
    
    (5)避免生成临时对象,尤其是大的临时对象
    
    (6)注意大尺寸对象数组
    
    (8)牢记80-20原则(软件整体的性能取决于代码组成中的一小部分):大约20%的代码使用
    了80%的程序资源;大约20%的代码耗用了大约80%的运行时间;大约20%的代码使用了80%
    的内存;大约20%的代码执行80%的磁盘访问;80%的维护投入于大约20%的代码上。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    22、兼容性

    (1)遵守ANSI C和ISO C++国际标准
    (2)确保类型转换不会丢失信息
    (3)注意双字节字符的兼容性(中文字符编码)
    (4)注意运算溢出问题
    (5)不要假设类型的存储尺寸
    (6)不要假设表达式的运算顺序
    (7)不要假设函数参数的计算顺序
    (8)不要假设不同源文件中静态或全局变量的初始化顺序
    (9)不要依赖编译器基于实现、未明确或未定义的功能
    (10)将所有#include的文件名视为大小写敏感
    (11)避免使用全局变量、静态变量、函数静态变量、类静态变量。在使用静态库,动态库,
    多线程环境时,会导致兼容性问题
    (12)不要重新实现标准库函数,如STL已经存在的
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    注:本文章参考了《朱老师物联网大讲堂》课程笔记,并结合了自己的实际开发经历以及网上他人的技术文章,综合整理得到。如有侵权,联系删除!水平有限,欢迎各位在评论区交流。

  • 相关阅读:
    信息学奥赛一本通 连接格点
    ElementUI的bug/方案/使用技巧合集,持续更新(最新22-7-4)
    Java毕业设计-网上宠物店系统
    Linux 编译安装PostgreSQL
    Excel快速定位sheet
    GBASE 8s dbspace配置参数
    树上背包问题动态规划
    [附源码]Python计算机毕业设计Django毕业生就业管理系统
    Vue基础语法【上】
    golang学习笔记系列之标准库time的学习
  • 原文地址:https://blog.csdn.net/weixin_45842280/article/details/127398534