《C++编程规范》

第一章 命名原则

1.1 关于类型名

类型名找那个每个英文单词的第一个字母大写,其他小写,最后以_T结尾。类型名包括class、struct、uinon、typedef、enum以及namespace的名字。
注意:缩写字当作普通字处理,即只有首字母大写

1.2 关于变量和函数名

变量和函数名中首字母小写,其后每个英文单词的第一个字母大写,其他小写。
注意:所写字当作普通字处理,即只有首字母大写

1.3 关于全大写的函数名

有一类函数,它们调用普通函数,只是对普通函数的错误返回做一般化处理。这些函数的名字要和所包含的函数名相同,只是全用大写字母(必要时用下划线分割名字中的英文单词)

1.4 关于宏、常量和模板名

这些名字要全部大写,如有多个单词,用下划线分隔
宏指所有用宏形式定义的名字,包括常量类和函数类,常量也包括枚举中的常量成员。
在这里插入图片描述
1.5 关于指针标识符名

建议以p开头或以Ptr结尾

在这里插入图片描述
1.6关于变量名前缀

用下面不同的前缀来修饰变量名以区分不同的作用域
i_ (instance scope) 类内数据成员(对象级成员);
c_ (class scope) 类内静态数据成员(类级成员);
g_ (global scope) 全局变量;
f_ (file scope) 文件作用域变量(静态变量)
函数内部等局部变量前不用前缀
在这里插入图片描述
1.7 关于匿名空间级标识符的前缀
给匿名命名空间级标识符一个公共前缀(如所属Package名或Library名,加下划线),用来区别其他提供类似功能的Packet或Library等。
匿名命名空间中的标识符值得是全局或文件级变量名、常量名、宏、类型名、函数名等。
前缀格式:全大写字母,(最好)少于3个字母

1.8 减少匿名命名空间级标识符
尽量减少匿名命名空间级变量、常量、宏及函数等标识符。可以归类放在某个命名空间、类或函数中。

1.9 尽量用可发音的英文名字

1.10 避免名字中出现形状混淆的字母或数字,如数字0和字母O,字母l和数字1

1.11 布尔型的名字要直观
is通常是一个不错的前缀,好不好可用if语句来检验

1.12 用lhs做左值参数的名字,用rhs做右值参数的名字

1.13 用a、an、any区分重名(参数)

第二章 类型的使用

2.1避免隐式声明类型
尽量用显式声明

2.2 慎用无符号类型

避免使用无符号类型,除非真的需要
按位访问的数据和设备寄存器通常要用无符号类型
原因:
混用有符号和无符号类型会导致奇怪的结果,因为其中会发生隐式类型转换
不同的C++标准中有符号和无符号的转换规则不同

2.3 少用浮点数除非必须
原因:浮点数不精确
因为计算机内部是二进制表示法,需要将代码中十进制浮点数转换成二进制。由于字长的限制,经常不得不丢掉最低的几位小数,所以结果是不精确的。
浮点数的异常处理复杂(比如上溢、下溢出等的处理)
运算速度慢

2.4 用typedef简化程序中的复杂语法

2.5 少用union
由于union成员公用内存空间,所以容易出错,并且维护困难
使用union通常意味着非面向对象的方法

2.6 用enum取代(一组相关的)常量
原因:

  • 易于维护
    枚举可以对其成员自动编号,这样增加或减少成员修改/维护工作就很方便
  • 比#define或int const更安全
    因为编译器会检查每个枚举值是否位于取值范围内
  • 在类中使用更方便
    如果用常量,要等到在构造函数中初始化后才能使用,而枚举不用
  • 减少匿名命名空间级变量、常量、宏及函数

2.7 使用内置bool类型
使用内置bool类型,而不是自己定义或用int代替
原因:内置bool类型比int更安全强壮
只有true和false两个值,而不是零或非零

2.8 尽量用引用取代指针
1.在下述情况下用引用优于指针:
2.被引用的对象永远不可能是空(NULL)
3.引用某一个对象后绝不会再去引用其他对象
4.某些operator函数的返回值,如operator[],operator+=等
函数的参数传递
但在下述情况下用指针更好:
1.NULL是合法的参数值
原因:
引用更安全,因为它一定不为空,且一定不会指向其他目标
不需要检查非法值(如NULL)情况
使用更简洁(“.”比“->”好用)
不需要解除引用:
与此相反,当所指的内存被释放后,指针应有一个合理的值,一般是让它指向NULL

第三章 函数

3.1 一定要做到先定义后使用
C++必须这样做(否则编译通不过)。C程序没有强制要求,但也应该先提供原型,再使用函数
原因:先定义使得编译器能够在编译时就检查和找出错误(而不是等到连接或运行时)
3.2 函数无参数一定要用void标注
在这里插入图片描述
原因:
C++和C对function()的解释不通。C++认为是不带参数;C认为是带任意参数(虽然ANSI C现在已经废除这一规则,但其他标准和编译器不一定能保证这一点)
显式使用(void)消除了C++和C混编时可能出现的潜在错误
3.3 对于内置类型参数应传值(除非函数内部要对其修改)

内置类型指int、char等(相对于自定义的class、struct、unit)
原因:
传值既安全又简单
内置类型拷贝的代价与传指针或引用相同,因为(绝大多数)内置类型所占内存小于等于指针或引用所占内存

3.4 对于非内置类型参数应传递引用(首选)或指针
非内置类型指的是自定义的类(class)、结构(struct)和联合(union)
如要防止参数被修改,可用const修饰
原因:

  • 不用拷贝

非内置类型的尺寸一般都大于引用和指针类型,尤其还要考虑非内置类型可能包含隐式的数据成员,比如虚函数指针等,所以拷贝的代价高于传递引用和指针

  • 不用构造和析构
    如果拷贝对象,还要再传入时调用(拷贝)构造函数,函数退出时还要析构

  • 有利于非内置类型的扩充
    对于小对象虽然传值代价也不大,但将来的修改/扩充可能使这一优势丧失,到时再漫山遍野地将函数接口从传值改成引用/指针就太费劲了。

  • 有利于函数支持派生类

  • 若将派生类对象传给以基类为参数的函数(传值方式),就会导致派生类对象被切割成基类对象,这样在函数内部实际上用的是一个基类对象的拷贝,而不是最初传入的派生类对象。这多半不是你的本意
    -这种错误非常难查,因为它是编译器(隐式/自动)做的;编译器也不会报警,因为这是合法的
    如果传入的是引用或指针,则不会有对象“切割”现象。函数内部可以将传入的对象视作基类对象使用(因为任何派生类对象都可以作为基类对象)。如果传入的对象有虚函数,则恰当的实现版本还会被正确调用,而不局限于传入的参数类型(基类)
    3.5 关于何时用指针传递参数

条件:

  • 若函数内部须将自己的参数以指针形式传给其他的函数
    因为至少你不能确定那些(需要指针的)函数一定不需要传NULL。你若在外面强行用引用,它们就再也无法获得NULL(作为参数值了)

  • 若参数是被new出来,且将要在函数内被释放
    如果用引用,则会出现这样的语句“delete &reference”,看起来有点怪(但不是绝对不可接受)
    原因:
    总的来说,引用比指针好,但指针也不是一无是处
    取舍的一个关键是:NULL是否是一个合法的取值
    3.6 避免使用参数不确定的函数
    原因:

  • 参数不确定的函数有隐患
    因为参数不确定,编译器就不能检查参数的个数和类型,这会带来很多问题

  • C++有很好的解决办法
    用重载和链式函数。比如printf()可以变成一系列只带一个参数的函数(比如operator<<()函数),每个函数都只接受一种printf()支持的类型,这些函数互为重载(函数名相同)。其结果是,任意一个printf()调用都可以转化为几个重载函数的连续调用
    可以看出,转化后更安全,因为每个重载函数的参数个数和类型都固定。同时也更灵活,因为增减一个类型只需增减一个重载函数,而不像printf()那样需要改函数实现和接口(接口上要相应增减类型指示符,如%s、%d等)

3.7 避免函数的参数过多
一个函数的参数应该限制在5个以内
3.8 尽量保持函数只有唯一出口
原因:
单一出口易于维护:修改代码容易,不易因为忘记修改某处出口而产生问题。
易于跟踪调试:可设单一端点跟踪函数(出口)
3.9 (非void)任何情况都要有返回值

任何非void函数在任何情况下都要返回某个值

3.10 若函数返回状态,尝试用枚举作类型
返回枚举类型可以使编译器对返回值做合法性检查(看看是不是枚举的合法成员)
3.11 返回指针类型的函数应该用NULL表示失败
原因:NULL是唯一合理的表示错误的指针返回值
同时也防止调用者用未初始化或已失效的指针进行随后的操作

3.12 函数尽量返回引用(而不是值),若必须返回值,不要强行返回引用
不要返回局部变量的引用,因为当函数返回后它就不存在了。
最好不要返回new出来的对象的引用,因为:
new出来的对象总要被删掉,而函数的返回值是匿名的(临时的),要保证不出内存泄漏(memory leak),调用者必须记下每个返回值,因为他们引用的是new出来的内存,但不再使用时要回收。通常这是一件困难的事情,有时甚至根本就做不到
当new出来的对象被删掉后,那个引用已经是无效的了,此时只能靠操作者记住这件事,这样非常容易导致错误。
3.13 禁止成员函数返回成员(可读写)的引用或指针
3.14 关于虚友元函数
如果我们能把有缘函数定义成虚函数,则子类既可以继承该有缘的接口而无需重读声明友元,(要知道,父类的友元不会自动成为子类的友元,而且友元会破坏封装,要少用),又能提供独特的子类实现。这个功能很诱人,然而C++语法不允许友元函数为虚函数。
3.15 关于虚构造函数
C++语法不允许构造函数为虚函数,但有时候需要由这样的函数:它们能根据不同情况构造不同的(派生)类。这类函数实际起到虚构造函数的作用。

第四章 类的设计和声明

4.1 类应是描述一组对象的集合
类应是描述一组对象的集合,而不是用来包裹一个简单对象(int、char等)
4.2 类成员应是私有的
每个类不应让其他人插手其内部实现(状态),所以类的成员不应是public;甚至连派生类也不应插手基类的内部,所以protected也应避免。
原因:
非private成员破坏了类的封装性,导致类本身不知道其成员何时被修改
更糟的是,任何对类的修改都将影响使用该类的代码,因为这些代码有权直接访问(被修改的)类成员。
4.3 保持对象状态信息的持续性

确保对象的状态信息(成员变量)在(对象的)整个生命周期都有效。
4.4 避免为每个类成员提供访问函数
原因:
如果每个类成员都有访问函数,那就类似于将所有成员都定义成Public。
对象的主要目的是捕捉发生在其上的行为,而不是像C语言的struct那样专注于属性(成员);为每个类成员提供访问函数将导致类的退化并偏离其主要目的。
专注于属性的类在多线程环境下效率低下。若专注于行为,一次行为会修改一连串相关属性而只需上一次锁(在每个类成员提供访问函数中),因为要防止多个线程同时调用访问函数(去修改同一个属性)。
4.5 函数声明(而不是实现)时定义参数的缺省值
原因:
函数带缺省值类似于重载函数,我们应在声明中让使用者知道此事,而不是等到实现时才说(使用者不该、而且也可能根本看不到实现,比如第三方类库)
在实现时给出缺省值还会导致信息分散(比如实现分散在多个文件中时)
4.6 恰当选择成员函数,全局函数和友元函数

1.虚函数必为成员函数,因为非成员函数不能为虚
2.operator>>和Operator<<必为非成员函数,因为其第一个参数是cin或cout等,而不是自己的类。
若需访问类成员,他们还必须是友元函数
3.若函数第一个参数(包括隐含参数,比如this)需要做隐式类型转换,则其必不能是成员函数,因为编译器不会对this做类型转换。
若需访问类成员,他们还必须是友元函数。
4.7 显式禁止编译器自动生成不需要的函数
编译器可自动/隐式生成缺省构造函数、拷贝构造函数、赋值函数等。如果不需要,一定显式禁止,特别不能认为“现在不会用到,以后用到再说”
显式禁止的原理
编译器一旦发现用户自己声明了那些函数,就不再自动生成。
声明为Private将导致类外无权访问,从而达到禁止类外使用被禁函数的目的。
不定义函数体则防止了类内及友元对其使用。
最终编译器在编译时阻止所有使用被禁函数的企图。
4.8 用嵌套的方法减少匿名命名空间类的数量

不需要把所有类的定义都放在匿名命名空间中。嵌套在别的类中是不属于匿名命名空间的。
原因:
减少命名冲突
使用者可以不再关心那些被嵌套的类(的具体实现),因为它们是内部使用的。

第5章 (面向对象的)继承

5.1 “公共继承”意味着“派生类是基类”
若类B公共继承于类A,则B的对象也就是A的对象,反之则不然。
5.2 关于“有”和“由…”实现
(一个类)包含(另一个类)意味着“有一个”或“由…实现”。私有继承意味着“由…实现”
“公共继承”、“私有继承”和“包含”的相互关系
“公共继承”和“包含”的区别:
“包含”的两个类不能互相替换(马不是腿,腿也不是马),而“公共继承”的派生类可以替换基类(白马是马,但反过来不行,马不是白马)
“公共继承”和“私有继承”的区别:
编译器不会将“私有继承”的派生类转换为基类,也就是说,“私有继承”的基类和派生类也没有谁是谁的关系。
“私有继承”和包含的关系:
基本一样
“私有继承”可以访问基类的protected成员,而包含不能访问被包含类的protected成员。
“私有继承”还可以重写基类的虚函数,而“包含”做不到
但是应尽量“包含”,只有在必要时才选择“私有继承”,因为它不如“包含”简单直观,且更容易和“公共继承”混淆。
5.3 关于继承接口和继承实现
纯虚函数:只继承接口并强制派生类必须提供自己的实现
一般的虚函数:继承接口并提供缺省实现
非虚函数:继承接口和实现(一点也不能改)
私有继承:只继承实现,因为接口在私有继承后都成为private
纯虚函数是可以有函数体的。许多人误认为纯虚函数一定不能有函数体,这是错误的。该函数体的作用也是提供缺省实现。但是如果派生类要使用该缺省实现,必须在自己的函数体中显式调用。
5.4 限制继承的层数
当继承的层数超过了5层时问题就很严重了,需要有特别的理由和做特别解释。
原因:
很深的继承通常意味着未做通盘考虑的设计
会显著降低效率
可以尝试(类的)组合替代(过多的)继承
与此类似,同层类的个数也不能太多,否则应考虑是否要加一个父类,以便做某种程度上的(新的)抽象,从而减少同层类的个数
5.5 继承树上非叶子节点的类应是虚基类
若决定不作虚函数,一定要有特殊理由。“导致效率下降”的理由不充分。
基类-派生类最常见的用法是:基类提供接口,派生类提供实现。这样,使用者就不用关心到底是哪个派生类提供实现,这就是所谓的多态。使用时用基类指针或引用指向/引用派生类的对象,而所有的操作是在这些基类指针/引用上进行的。若基类析构不是虚函数,这种用法就有问题:基类指针或引用只能调用基类的析构函数,从而使得派生类对象析构不成功。
5.6 所有多重继承的基类析构函数都应是虚函数
目的是当析构派生类对象时,确保所有析构函数都被正确调用。

第6章 内存分配和释放

6.1 用New、delete取代malloc、calloc、realloc和free

6.2 new、delete和new[]、delete[]要成对使用
调用new 所包含的动作
从系统堆中申请恰当的一块内存
若是对象,调用相应类的构造函数,并以刚申请的内存地址作为this参数
调用new[n]所包含的动作
从系统堆中申请可容纳n个对象外加一个整型的一块内存
将n记录在额外的那个整型内存中(其位置依赖于不同的实现,有的在申请的内存块开头,有的在末尾)
调用n次构造函数初始化这块内存中的n个连续对象
调用delete所包含的动作
若是对象,调用相应类的析构函数(在delete参数所指的内存处)
将该内存返还给系统堆
调用delete[]所包含的动作
从new[]记录n的地方将n值找出
调用n次析构函数析构这块内存中的n个连续对象。
将这一整块内存(包括记录m的整型)归还系统堆。
6.3 当对象消亡时确保指针成员指向的系统堆内存全部被释放
原因:

-否则会造成内存泄漏

  • 将相应的指针成员替换成自动指针对象是一个一劳永逸的办法
    因为若对象正常消亡,系统会在其析构函数执行完成后自动调用所有成员的析构函数(如果有的话),所以自动指针成员会在此时释放其拥有的系统堆内存。
    无论发生什么异常,只要对象能被析构,自动指针就会释放其拥有的系统堆内存。
    对象在构造时发生异常,其析构函数是不被调用的,因为该对象还未构造完成。但即便如此,C++的异常处理机制也会依次调用已构造好的成员的析构函数。所有已申请的内存还是能被释放掉的。
    6.4自定义类的new操作符一定要自定义类的delete操作符
    内存资源从哪里、以何种方式申请到的,还应以相同的方式返回。
    6.5 当所指的内存被释放后,指针应有一个合理的值
    除非该指针变量本省将要消失,否则应置为空(NULL)
    原因:
    防止其后再次使用该指针
    防止其后再次删除该指针

第七章初始化和清除

7.1 声明后就初始化强于使用前才初始化
在这里插入图片描述
原因:
减少使用未初始化对象的几率
减少临时状态
提高效率

7.3 初始化要彻底

在这里插入图片描述
原因:
初始化不彻底的对象不是可用状态
稍不注意,就有可能在完全初始化前使用
7.4 确保每一个构造函数都实现完全的初始化
7.5 构造函数没结束,对象就没有构造出来
构造函数结束以前,系统将相应对象视为不存在(因为不完全),此时很多事情不能做:

  • 不能调用析构函数

不但编程者不能,编译器也不能:比如隐式调用的构造函数未结束(被打断),编译器永远不会隐式调用析构函数,因为编译器认为该对象没有构造出来

  • 构造函数全程不要使用this成员,因为this所指的对象还不完整
  • 不要在构造函数中调用虚函数:
    因为构造函数会先调所有的基类构造函数,等待所有的基类都构造好之后再构造自己;但所有的基类构造函数所用的this参数,其类型均为派生类(而非该基类),所以(基类)构造函数若调用虚函数,执行的函数体可能是派生类的成员函数,但此时派生类自己还未开始构造。

7.6 拷贝构造函数和赋值函数尽量用常量参数
一般来说,拷贝和赋值过程中源对象不会被修改,只有目的对象被修改,所以构造函数和复制函数的参数应该是常量。
变量可以当常量用,所以变量可以传给参数是常量的拷贝或赋值函数(编译器会将变量隐式转换为常量,再传给函数)
7.7 让赋值函数返回当前对象的引用,并在赋值函数中防范自己赋值自己
7.8 关于构造函数、析构函数、赋值函数、相等或不等函数的格式
在这里插入图片描述
7.9 为大多数类提供缺省和拷贝构造函数、析构函数、赋值函数、相等函数
原因:
包含全部这些函数的类通常被视为符合惯例:
缺省构造函数在定义相等数组情况下是必不可少的
如需以值形式向函数传递对象,必须有拷贝构造函数
析构函数永远都有,编程者不提供,编译器也会自动生成一个空的析构函数。需要注意的是,析构函数经常是虚函数,但自动生成的析构函数不能保证这一点(如果父类析构函数是虚函数,则自动生成的析构函数也是虚函数,否则是普通函数)
自定义类就像普通类型一样,经常需要相互赋值和判断相等与否,所以赋值函数和相等函数经常是不可或缺的。
7.10 包含资源管理的类应自定义拷贝构造函数、赋值函数和析构函数
如果类不土工这三种函数,编译器可以自动生成。但自动生成的拷贝构造函数和赋值函数只能将所有源对象的成员简单赋值给目的对象,所谓浅拷贝,而自动生成的析构函数根本就是空的。这对于包含资源管理的类是不够的:比如包含从系统堆中申请的内存(资源),浅拷贝会使源和目的对象的成员指向同一(申请)内存,而空析构函数没有将申请的内存释放掉。此时需要自定义这些函数,以完成正确的动作。
除了系统堆内存以外,文件、管道、(网络)连接等都是资源。
7.11 拷贝构造函数、赋值函数和析构函数要么全自定义、要么全生成
7.12 若编译时会完全初始化,不要给出数组的尺寸
在这里插入图片描述
让编译器计算数组的尺寸会使你的代码能自适应数组的变化

第8章 常量

在这里插入图片描述
8.1 常量成员函数
常量成员函数是指函数体不会对任何成员(包括基类成员,但不包括静态成员)进行修改的成员函数。其函数声明以关键字const结尾。其实,常量成员函数就是其隐含的this参数为指向常量对象的指针,但因为this隐含,无法将const修饰符加上,只能放在结尾。由此可以推断出一下原则:
若成员函数在设计时不会修改任何成员,则应定义成常量成员函数。这样常量对象和非常量对象都可以使用该函数。
常量成员函数和同名同参数(显式)的非常量成员函数可重载,这是因为他们的第一个(this)参数类型不同。
**

第9章 操作符

**
在这里插入图片描述
9.1 区分作为成员函数和作为友元的操作符
若操作符需要左值,应将其定义为成员函数,否则应是友元。
操作符operator+=()、operator=()等都需要左值,应是成员函数;而操作符operator==()、operator+()不需要左值,应是友元。
原因:
操作符作为成员函数可以确保左值(被赋值者)一定是该类的对象。
操作符作为友元,使得第一个操作数可以是任何类型不一定非要是自定义类
参考书籍:《C++编程规范》

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
C编程规范PDF是一种编程规范的文档形式,用于指导和规范C语言编程的风格和标准。通过遵循C编程规范,可以提高代码的可读性、可维护性和可移植性,从而减少错误和提高开发效率。 C编程规范PDF通常包含以下内容: 1. 命名约定:指定变量、函数、宏等的命名规则,例如使用有意义的名称,避免使用缩写或数字开头等。 2. 缩进和格式化:定义缩进和代码格式化的规则,例如使用空格或制表符进行缩进,并规定代码块的大括号的位置等。 3. 注释规范:规定注释的格式和位置,描述代码的功能、作者、日期等信息,并提供必要的注释以增加代码的可读性。 4. 函数和变量声明:规定函数和变量的声明方式,例如声明的位置、类的命名规则、参数的顺序和命名等。 5. 错误处理:规定如何处理错误和异常情况,例如使用错误码、异常处理、日志记录等方式。 6. 编程技巧和最佳实践:提供一些编程技巧和最佳实践,帮助开发人员避免常见错误和提高代码的质量。 7. 编程风格:规定编码风格的一致性,例如使用驼峰命名法、每行最大字符数、运算符的位置等。 通过使用C编程规范PDF,开发人员可以遵循一致的编码规则,减少代码错误和风格上的不一致,提高代码的可读性和可维护性。这对于团队开发尤其重要,因为不同开发人员的风格和习惯可能有所不同。同时,C编程规范PDF还可以作为培训和文档的参考,帮助新的开发人员快速适应团队的编码规范

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Estelle_Z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值