首页 > 知识百科 > 正文

《高质量的C/C++编程规范》学习原创

目录

一、编程规范基础知识

1、头文件

2、程序的板式风格

3、命名规则

< p id="%E4%BA%8C%E3%80%81%E8%A1%A8%E8%BE%BE%E5%BC%8F%E5%92%8C%E5%9F%BA%E6%9C %AC%E8%AF%AD%E5%8F%A5-toc" style="margin-left:0px;">二、表达式和基本语句

1、运算符的优先级

2、复合表达式

3、if语句

4、循环语句的效率

5、for循环语句

 6、switch语句

三、常量

1、#define和const比较

2、常量定义规则

四、函数设计

1、参数规则

< p id="2%E3%80%81%E8%BF%94%E5%9B%9E%E5%80%BC%E8%A7%84%E5%88%99%C2%A0%20%C2% A0-toc" style="margin-left:40px;">2、返回值规则   

3、函数内部实现规则

4、断言

五、内存管理

1、内存分配

2、托盘与负载

3、免费和删除

 4、动态内存释放

 5、内存消耗

6、malloc和free使用


一、编程规范基础知识

1、头文件

(1)防止头文件被包含

       首先,和大家闲聊一聊头文件为什么会被重复包含呢。这个错误操作主要是因为include塑造造成的。举个例子,在a.h文件中#include "c.h",而在b.c文件中#include "a.h"和#include "c.h"。此时,就造成了c.h重复包含。

        头文件被有些头文件重复引用,只是增加了编译工作的工作量,不会引起严重的问题,其实编译效率低一些,但是对于大工程来说编译效率就是很重要的了;有些头文件重复包含,会引起编译错误,比如在头文件中定义了全局变量或写了函数的实现而不是声明(虽然这种方式不被推荐,但确实是C规范允许的)的),这种会引起重复定义。

       那,如何避免头文件被重复包含呢?我们可以使用#ifndef/#define/#endif 方式,下面我们给出该解决方法来使用:

#ifndef __XXX_H__ //意思是"if not Define __XXX_H__" 根本没包含XXX.h #define定义__XXX_H__ //就__XXX_H__ .../ /此处放头文件中本来应该写的代码 #endif //否则不需要定义 

(2)引用头文件

< >头文件:引用标准库的头文件,编译器将,从标准目录开始搜索。 " "头文件:引用非标准库的头文件,用户的工作目录开始搜索,用户自己创建的头文件。

2、程序的板式风格

        清晰、美观,是程序风格的重要构成因素。

(1)空行

        我们在编程时可以使用空行(衍生出分隔程序的作用)。空行,和大家下面下面三点规则:空行不会浪费内存;在每类声明之后、每个函数定义结束之后都要聊加空行;在一个函数内部,逻辑揖上密切相关的语句之间不加空行,其他地方应加空行分隔。

(2)代码行

        一行代码只做一件事情,如只定义一个变量,或者只写一条语句。这样的代码很容易阅读关于代码行,主要是以下几点规则:

规则一:if、for、while、do等语句单独占一行,执行语句不一定紧跟其后。执行语句有多少后面要加{}。这样可以防止书写失误。

规则二:关键字之后要留空格。像 const、virtual、inline、case 等关键字之后至少要留一个空格,否则无法辨析关键字。像 if、for、while 等关键字之后要留一个空格再跟左逗号'(',以突出关键字。

规则三:如果';'不是一行的结束符号,其后要留空格,如for(initialization; condition; update)。

规则四:赋值操作符、比较操作符、算术操作符、逻辑操作符、位域操作符,如“= ”、“+=”“>=”、“<=”、“+”、“”、“%”、“&&”、“||”、“<<”、“^”等二元操作符的左右须加空格。

(3)对齐

       关于对齐讲两点规则:

规则一:程序的分界符'{'和'}'应独占一行且位于同一列,同时与引用它们的语句左对齐。

规则二:{ }之内的代码块在'{'右侧数格处左对齐。

(4)长行拆分

        每条代码行不要过长,不利于观看和打印。长表达式要在低优先级操作符处分割成新行,操作符放在新行之首(以便突出操作符)。分割出的新行要进行适当的缩进,使排版顺序,语句强制。

(5)修饰符

        修饰符位置: * 和 & 紧靠变量名。

        int*  x, y; // 这样写,y很容易被误解为指针指针。应该修改为 int *x, y,这样y就不会再被误解成指针了。

(6)注释

        如果代码本来就是清楚的,则必要加注释。否则多此一举,令人厌烦。注释的花样要少 。注释的位置应与描述的代码后续,可以放在代码的上方或有方,不可放在下方。当代码比较长,特别是有循环循环时,应在一些段落的结尾处加注释,并阅读。

3、命名规则

        Windows应用程序的标识符通常采用“大小写”混排的方式,如AddChild。而Unix应用程序的标识符通常采用“小写加下划线”的方式,如add_child。接下来讲几点常见规则:

规则一:类名和函数名用大写字母开头的单词组成。

规则二:变量和参数用小写字母开头的单词组成。

规则三:常量全用大写字母,用下划线分割单词。

规则四:静态变量加外接s_(表示静态)。

规则五:如果使用全局变量,则使全局变量加外接g_(表示全局) 。

二、表达式和基本语句

1 、运算符的优先级

        如果代码行中的运算符比较多,用逗号确定表达式的操作顺序, 避免使用错误默认的优先级。

比如: if ((a | b) && (a & c)) 

2、复合表达式< /h3>

       关于复合表达式,这里讲解以下三点规则:

规则一:不要编写太复杂的复合表达式。

规则二:不要有多用途的复合表达式。例如:d = (a = b + c) + r ,该表达式既求 a 值又求 d 值。应分割为两个独立的语句。

规则三:不要把真正程序中的复合表达式与“的数学表达式”相互矛盾。 例如: if (a < b < c) 。在这条语句里 a < b < c 是数学表达式而不是程序表达式式中,所以该语句并不表示 if ((a

3、if语句

(1)布尔信号与零值比较

        不可将布尔信号直接与TRUE、FALSE或1、0进行比较。根据布尔类型的语义,零值为“假”(记为

假设布尔变量名称为flag,它与零值比较的标准if语句如下:

 <代码类=“hljs”>- if (flag) 表示 flag 为真 - if (!flag) 表示 flag 为假

(2)整型变量与零值比较

        应当将整型变量与“==”或“!=”直接与 0 比较。假设整型变量的名称为值,它与零值比较的标准 if 语句如下:

if (value == 0) if (value != 0) 

(3)浮点指标与零值比较

        不可将浮点指标与“==”或“!=”与任何数字比较。无论是float还是double类型的变量,都有精度限制。一定要避免将浮点变量用“==”或“!=”与数字比较,比较应该设法转化成“>=”或“ <=”形式。

假设浮点变量的名字为x,应当将if (x == 0.0)隐含错误的比较,转化为if ((x >=-EPSINON) && (x<=EPSINON)) //其中EPSINON是允许的精度(即精度)。EPSINON是e的负10次方,该数表示接近于0的小正数。 

(4)铲斗指针与零值比较

        应当将铲斗指针用“==”或“!=”与 NULL 比较。铲斗指针的零值比较是“空”(记为NULL)。NULL的值与0相同但两者意义不同。假设指针变量的名称为p,它与零值比较的标准if语句如下:

< code class="6f1e-06c1-2c5d-e56a hljs">if (p == NULL) // p 与 NULL 显式比较,强调 p 是指针指针 if (p != NULL) 

4 、循环语句的效率

        本块重点讲述循环体的效率。提高循环体效率的基本方法是降低循环体的复杂性。接下来讲解两种主要规则:

规则一:在时钟循环中,如果有可能,应当将一个的循环放在最内层,最短的循环放在最外层,以减少cpu跨切循环层的次数。如下图所示:

for (i=0; i<5; i++ ) {for (j=0; j<100; j++) { sum = sum + a[j][i] ; } } 

规则二:如果循环内部存在逻辑判断,并且循环次数很大,宜将逻辑判断移到循环体外部。如下图所示:

< pre>for (i=0; i

5、对于循环语句

        不可在循环内修改循环变量,防止循环失去控制。循环控制变量的取值建议采用“半开半闭区间的写法”。如图:

 6、switch语句

        switch是多分支语句,格式如下:

switch (variable) { case value1: break;案例值2:中断;默认:中断; }

规则一:每个case语句的结尾不要忘记加break,否则将导致多个分支重叠(避免浪费使用多个分支重叠)。

规则二:不要忘记最后那个默认分支。程序确实不需要默认处理,也应该保留语句默认:break;即使这样做也不多此一举,而是为了防止别人误以为你忘记了默认处理。

三、常量

1、#define和const比较

(1) const常量有数据类型,而宏常量没有数据类型。编译器可以对前面进行类型安全检查。而对晚上只进行字符替换,没有类型安全检查,字符替换可能会产生意料不到的错误(边际效应)。

(2)一些集成化的调试工具可以对const常量进行调试,但不能对宏常量进行调试。

(3)在c++程序中只使用const常量而不使用宏常量,即const常量完全替代宏常量。

2、常量定义规则

 规则一:需要对外公开的常量放在头文件中,不需要对外公开的常量放在定义文件的头部。为进行管理,可以把不同模块的常量集中存放在一个规则二:如果常量与其他常量密切相关,应在定义中包含这种,而不应给出一些孤立的值。例如:

const float 半径 = 100; const float 直径 = 半径 * 2;

四、函数设计

        函数接口的两个参数是参数和返回值。C语言中,函数的参数和返回值的传递方式有两种:值传递和指针传递。C++语言中多了引用传递(引用传递的性质像指针传递,而使用方式却像值传递)。

1、参数规则

规则一:参数的书写要完整。如果函数没有参数,则用 void填充。

规则二:参数配置要首先,顺序要合理。一般应将目的参数放在前面,源参数放在后面。

规则三:如果参数是指针,且仅作输入用,则应在前面加 const,以防止该指针在函数体内被意外修改。

void StringCopy(char *strDestination,const char *strSource);

规则四:如果输入参数以值传递的方式来传递对象,则宜用“const &”方式来传递,这样省去临时对象的构造和分析过程,从而提高效率。

2、返回值规则   

 (1)不要省略返回值的类型

        C语言中,凡不加类型说明的函数,一律自动按整型处理。这样做易被误认为是void类型。C++语言有严格的类型安全检查,不允许发生上述情况。由于C++可以调用C函数,为了避免崩溃,规定任何c /c++ 函数都必须有类型。如果函数没有返回值,则应声明为void类型。

(2)函数名称与返回值类型在语义上不可冲突

< /p>

(3)不要将正常值和错误标志混在一起返回。正常值用输出参数获得,而错误标志用返回语句返回

        正常情况下,getchar不会返回单个但如果getchar文件结束标志或发生读取错误,它必须返回一个标志EOF。为了区别于正常的字符,只好将EOF定义为负数(通常为-1)。因此函数getchar就变成int类型。

3、函数内部实现规则

(1)在函数的“入口处”,对参数的有效性进行检查

        很多程序错误是由非法参数引起的,应充分理解并正确使用断言“assert”来防止此类错误。

(2) 在函数的“出口处”,对返回语句的正确性和效率进行检查。

         返回语句不可指向“堆栈内存”的“指针”或者“引用",因为该内存在函数体结束时被自动调用。强行使用,会造成非法访问(等同访问了一块不再属于你的空间)。

4、断言

        程序一般分为调试版本和发布版本,调试版本用于内部调试,发布版本发布给用户使用。

        断言assert只是在调试版本作业的宏,他用于检查“不应该”发生的情况。在运行过程中assert的参数为假,那么程序就会

        如果程序在assert处终止了,并不是说该assert的函数有错误,而是调用者产生了差错,assert可以帮助我们找到发生错误的原因。

(1) 使用断言捕捉不应该发生的非法情况。不要混淆非法情况与错误情况之间的区别,错误情况是必然存在的并且一定要做出处理。

(2 ) 在函数的入口处,使用断言检查参数的有效性(合法性)。

(3) 在编写函数时要进行反复论证,如果不可能的事情发生了,则使用断言进行报警。

五、内存管理

1、分配内存

内存分配方式有明显:
(1)从静态存储区域分配

         内存在程序编译的时候就已经分配好了,这块内存在程序的整个运行期间都存在。例如全局变量,静态变量。
(2)在栈上创建

        在执行函数时,函数局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配占用内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
(3)从堆上分配,亦称动态内存分配

        程序在运行的时候用malloc或new申请多少内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
        这里关于内存分配不过多赘述了,这里对不熟悉的同学,推荐看这篇文章---《C语言内存空间布局》,讲解十分之一详细。

简单总结一下内存分配的几种常见错误吧!

(1) 内存分配未成功,但却使用了它。< /p>

(2)内存分配成功,但尚未初始化就引用它。

(3)内存分配成功且初始化已,但操作已越内存的边界。

< p>(4)忘记释放内存。动态内存的申请与释放必须配合,程序中malloc与free的次数一定要相同,否则肯定有错误(new与delete同理)。

( 5) 内存释放了但继续使用它。

2、备份与备份

(1& #xff09;对比

         集群在静态存储区(全局集群)或者在栈上被创建。 集群名对应着(而不是指向)内存,其地址与容量在生命维持保持不变,只有卸载的内容可以改变。

        指针可以随时指向任何类型的内存块,它的特征是“可变”,常用指针来操作动态内存。指针远比指针可用,但也更危险。

(2)内容的复制与比较

        不能对数据库名进行直接复制与比较。若想把导入a的内容复制给导入b,应该用标准库函数strcpy进行复制。比较a与b的内容是否符合,也应该用标准库函数strcmp进行比较。

        若要复制导入a的内容,应用malloc申请strlen(a)+1个字节的内存空间,再用strcpy进行字符串复制。

(3)内存容量

        使用运算符sizeof可以计算出系统的容量(字节数) 。

        C/C++ 没有办法知道指针所指的内存容量,除非在申请内存时记住它。

3、free和delete

        free和delete只是把指针所指向的空间释放掉,但并没有把指针本身干掉。

        切记要初始化指针,释放指针后要置成空指针。或者将指针设置为NULL,或者使指向合法的内存。例如:

 char *p = 努二; * char *str = (char *) malloc(100); 

 4、动态内存释放

(1)剪刀消亡了,并不代表他所指的内存会被自动释放。

(2)内存被释放了,并不表示指针会消亡或者变成了NULL指针。

(3)程序终止运行,一切指针都会消亡,动态内存会被操作系统恢复。

 5 、内存运动

        如果在动态内存时找不到足够大的内存块,malloc 和 new 将返回 NULL 指针,申请判定内存失败。通常有明确的方式处理“内存运动”问题

(1) 判断指针是否为NULL,如果是则立即用返回语句终止本函数。

(2) 判断指针是否为NUL,如果是则立即用退出(1) 终止整个程序的运行。

(3) 为new和malloc设置异常处理函数。

        对于32位以上的应用程序而言,无论怎样使用malloc与新的,几乎不可能导致“内存疲劳”。

6、 malloc和free的使用

malloc函数原型如下:

 int *p = (int *) malloc(sizeof(int) * length);< /code>

(1)malloc返回值为void*,在malloc时要显着地进行类型转换,将void*转换成所需要的指针类型。p>

(2)malloc本身函数不识别要申请的内存是什么类型,他只关心内存的总字节数。

(3)在malloc的"()"中使用sizeof函数是良好的风格。

free函数原型如下:

void free(void *memblock);

         为什么free函数不像malloc函数那样复杂呢?因为这是指针的类型以及它所指向的内存容量事先都是知道的,语句free()能正确地释放内存。如果该指针是NULL指针,无论那么free对指针操作多少次都不会出现问题。如果该指针不是NULL指针,那么free连续操作两次就会导致程序运行错误。

本篇文章是我在学习林锐博士的《高质量的C/C++编程》以及其他一些网络资料之后进行的一篇自我学习总结。由于对C++还不是很熟悉,所以本篇文章主要总结了C语言部分,在日后研究学习C++时,在进行详细补充。如果本篇文章哪里出现问题,谢谢大家能够指正。

《高质量的C/C++编程规范》学习原创由知识百科栏目发布,感谢您对的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“《高质量的C/C++编程规范》学习原创

Copyright © 2012-2023 普诚元亨工作室 版权所有

*本站部分网页素材及相关资源来源互联网,如有侵权请速告知,我们将会在24小时内删除*

Z-BlogPHP 1.7.3 琼ICP备2022020219号