《《高质量编程规范》PPT课件》由会员分享,可在线阅读,更多相关《《高质量编程规范》PPT课件(61页珍藏版)》请在金锄头文库上搜索。
1、上章回顾上章回顾哈希函数的构造方法有那些哈希表中处理冲突的方法有那些高质量编程规范高质量编程规范高质量编程规范高质量编程规范第八章第八章第八章第八章预习检查预习检查C语言要经历哪几个编译过程如何申请链表单元,及释放链表单元实现单链表插入的基本语法简述一下快速排序基本理论要点课程目标课程目标本章概述本章概述阐述如何进行高质量的编程,以及注意事项本章目标本章目标了解高质量编程注意的方方面面从代码风格,算法,方便调试,性能等。重点重点内存分配与释放,悬挂指针本章结构本章结构程序员的态度程序员的态度高质量编程规范高质量编程规范微观上高质量微观上高质量宏观上高质量宏观上高质量8.1程序员的态度程序员的态
2、度程序员的弱点不太愿意测试自己的代码 不愿意REVIEW团队队员的代码程序员重点保证自己的代码没有 BUG 来 8.1程序员的态度程序员的态度程序员自身可以在程序生成的流程 详细设计 编写代码单元测试 功能测试 代码 REVIEW 8.1.1编码的风格编码的风格版权和版本的申明头文件的结构定义文件的结构头文件的作用目录结构命名规则注释规则/*Copyright(c)2001,上海贝尔有限公司网络应用事业部上海贝尔有限公司网络应用事业部*Allrightsreserved.*文件名称:文件名称:filename.h*文件标识:文件标识:见配置管理计划书见配置管理计划书*摘摘要:要:简要描述本文件
3、的内容简要描述本文件的内容*当前版本:当前版本:1.1*作作者:者:输入作者(或修改者)名字输入作者(或修改者)名字*完成日期:完成日期:2001年年7月月20日日*取代版本:取代版本:1.0 *原作者原作者:输入原作者(或修改者)名字输入原作者(或修改者)名字*完成日期:完成日期:2001年年5月月10日日8.1.1.1版权和版本的申明版权和版本的申明版权和版本的声明位于头文件和定义文件的开头,主要内容有:(1)版权信息。(2)文件名称,标识符,摘要。(3)当前版本号,作者/修改者,完成日期。(4)版本历史信息。范例8.1.1.2头文件的结构头文件的结构头文件由三部分内容组成:头文件开头处的
4、版权和版本声明。预处理块。函数和类结构声明等。范例为了防止头文件被重复引用,应当用ifndef/define/endif结构产生预处理块。用 #include 格式来引用标准库的头文件(编译器将从标准库目录开始搜索)。用 #include “filename.h” 格式来引用非标准库的头文件(编译器将从用户的工作目录开始搜索)。头文件中只存放“声明”而不存放“定义”不提倡使用全局变量,尽量不要在头文件中出现象extern int value 这类声明。8.1.1.3定义文件的结构定义文件的结构定义文件有三部分内容:定义文件开头处的版权和版本声明对一些头文件的引用程序的实现体(包括数据和代码)范
5、例/版权和版本声明版权和版本声明#include“graphics.h”/引用头文件引用头文件/全局函数的实现体全局函数的实现体voidFunction1()8.1.1.4头文件的作用头文件的作用通过头文件来调用库功能头文件能加强类型安全检查8.1.1.5目录结构目录结构特点:便于维护通常应将头文件和定义文件分别保存于不同的目录加强信息隐藏:如果某些头文件是私有的,它不会被用户的程序直接引用,则没有必要公开其“声明”范例: Network 工程建立三个目录 source:存放工程源文件,如:server.c Client.c Include:存放工程头文件,如:server.h Client.
6、h Lib:存放工程库文件,如:tipr.so stdio.so8.1.1.6命名规则命名规则主要思想:在变量和函数名中加入前缀以增进人们对程序的理解具体规则:标识符应当直观且可以拼读,可望文知意,不必进行“解码”。标识符的长度应当符合“min-length & max-information”原则命名规则尽量与所采用的操作系统或开发工具的风格保持一致程序中不要出现仅靠大小写区分的相似的标识符程序中不要出现标识符完全相同的局部变量和全局变量变量的名字应当使用“名词”或者“形容词名词”用正确的反义词组命名具有互斥意义的变量或相反动作的函数等8.1.2程序的版式程序的版式空行代码行代码行内的空格代
7、码对齐长行拆分修饰符的位置注释8.1.2.1空行空行空行起着分隔程序段落的作用。空行得体(不过多也不过少)将使程序的布局更加清晰。空行不会浪费内存空行规则每个函数定义结束之后都要加空行在一个函数体内,逻揖上密切相关的语句之间不加空行,其它地方应加空行分隔。8.1.2.2代码行规则代码行规则一行代码只做一件事情如只定义一个变量,或只写一条语句。这样的代码容易阅读,并且方便于写注释。if、for、while、do等语句自占一行,执行语句不得紧跟其后。不论执行语句有多少都要加。尽可能在定义变量的同时初始化该变量(就近原则)8.1.2.3代码行内的空格代码行内的空格关键字之后要留空格函数名之后不要留空
8、格,紧跟左括号(,以与关键字区别(向后紧跟,)、,、;向前紧跟,紧跟处不留空格,之后要留空格二元操作符的前后应当加空格。一元操作符前后不加空格。象“”、“.”、“-”这类操作符前后不加空格。8.1.2.4对齐和拆分规则对齐和拆分规则对齐规则程序的分界符和应独占一行并且位于同一列,同时与引用它们的语句左对齐 之内的代码块在右边数格处左对齐。长行拆分规则代码行最大长度宜控制在70至80个字符以内长表达式要在低优先级操作符处拆分成新行,操作符放在新行之首(以便突出操作符)。拆分出的新行要进行适当的缩进,使排版整齐,语句可读8.1.2.5长行拆分规则长行拆分规则代码行最大长度宜控制在70至80个字符以
9、内 长表达式要在低优先级操作符处拆分成新行,操作符放在新行之首(以便突出操作符) 例:if(very_longer_variable1=very_longer_variable12)&(very_longer_variable3=very_longer_variable14)&(very_longer_variable5= b & c d & c + f = g + h ; 不要有多用途的复合表达式 d = (a = b + c) + r ; 不要把程序中的复合表达式与“真正的数学表达式”混淆 if (a b c) 与 if (ab) & (b=”或“=-EPSINON) & (x=EPSIN
10、ON) 其中EPSINON是允许的误差(即精度)。 8.2.1.3If语句语句指针变量与零值比较指针变量与零值比较 指针变量用“=”或“!=”与NULL比较 例子: 与零值比较的标准与零值比较的标准if语句如下:语句如下:if(p=NULL)/p与与NULL显式比较,强调显式比较,强调p是指针变是指针变量量if(p!=NULL)不要写成不要写成if(p=0)/容易让人误解容易让人误解p是整型变量是整型变量if(p!=0)或者或者if(p)/容易让人误解容易让人误解p是布尔变量是布尔变量if(!p)8.2.1.4使用使用const提高函数的健壮性提高函数的健壮性const 用法:定义常量 修饰函
11、数的参数 修饰函数的返回值 修饰函数的定义体 8.2.1.4.1用用const修饰函数的参数修饰函数的参数const只能修饰输入参数 特点如果输入参数采用“指针传递”,那么加const修饰可以防止意外地改动该指针,起到保护作用 例:void StringCopy(char *strDestination, const char *strSource); 如果输入参数采用“引用传递 ”, 可以避免修改参数值的值传递void Func(const A &a) 8.2.1.4.2用用const修饰函数的返回值修饰函数的返回值如果给以“指针传递”方式的函数返回值加const修饰,那么函数返回值(即指针
12、)内容不能被修改例如函数const char * GetString(void);如下语句将出现编译错误:char *str = GetString();正确的用法是const char *str = GetString();8.3.1防止内存泄漏防止内存泄漏内存分配方式 malloc/free 的使用要点 常见的内存错误及其对策 引用与指针的比较 指针与数组的对比 指针参数是如何传递内存的动态内存自动释放 杜绝“野指针” 8.3.1.1内存分配方式内存分配方式内存分配方式有三种:从静态存储区域分配 在栈上创建 从堆上分配,亦称动态内存分配 malloc或new free或delete 8.3
13、.1.2malloc/free的使用要点的使用要点malloc语法:void * malloc(size_t size); 作用:申请一块长度为length的整数类型的内存 例子:int *p = (int *) malloc(sizeof(int) * length) Free语法:void free( void * memblock ) 作用:释放内存 例子:free(p)如果p是NULL指针,那么free对p无论操作多少次都不会出问题如果p不是NULL指针,那么free对p连续操作两次就会导致程序运行错误。8.3.1.3常见的内存错误及其对策常见的内存错误及其对策常见的内存错误 内存分配
14、未成功,却使用了它 内存分配虽然成功,但是尚未初始化就引用它 内存分配成功并且已经初始化,但操作越过了内存的边界 忘记了释放内存,造成内存泄露 释放了内存却继续使用它 8.3.1.3常见的内存错误及其对策常见的内存错误及其对策内存管理的规则 用malloc之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。避免数组或指针的下标越界,特别要当心发生“多1”或者“少1”操作。动态内存的申请与释放必须配对,防止内存泄漏。用free释放了内存之后,立即将指针设置为NULL,防止产生“野指针”。8.3.1.5指针与数
15、组的对比指针与数组的对比差别数组:要么在静态存储区被创建(如全局数组),要么在栈上被创建 数组名对应着(而不是指向)一块内存其地址与容量在生命期内保持不变只有数组的内容可以改变 指针:随时指向任意类型的内存块 动态生存在数据堆8.3.1.5指针与数组的对比指针与数组的对比差别内存的容量大小例:1例2chara=helloworld;char*p=a;sizeof(a)?/12字节字节sizeof(p)?/4字节字节 voidFunc(chara100)sizeof(a)?/4字节而不是字节而不是100字字节节 8.3.1.5指针与数组的对比指针与数组的对比动态内存会自动释放 不会自动释放例:指
16、针应该注意的特性指针消亡了,并不表示它所指的内存会被自动释放。内存被释放了,并不表示指针会消亡或者成了NULL指针。voidFunc(void)char*p=(char*)malloc(100);/动态内存会自动释放动态内存会自动释放吗?吗? 8.3.1.6指针参数是如何传递内存的指针参数是如何传递内存的*如果函数的参数是一个指针,不要指望用该指针去申请动态内存如果函数的参数是一个指针,不要指望用该指针去申请动态内存voidGetMemory(char*p,intnum)p=(char*)malloc(sizeof(char)*num);voidTest(void)char*str=NULL;
17、GetMemory(str,100);/str仍然为仍然为NULLstrcpy(str,hello);/运行错误运行错误8.3.1.6指针参数是如何传递内存的指针参数是如何传递内存的*如果函数的参数是一个指针,不要指望用该指针去申请动态内存如果函数的参数是一个指针,不要指望用该指针去申请动态内存voidGetMemory2(char*p,intnum)*p=(char*)malloc(sizeof(char)*num);voidTest(void)char*str=NULL;GetMemory(&str,100);/str仍然为仍然为NULLstrcpy(str,hello);/运行错误运行错
18、误8.3.1.6指针参数是如何传递内存的指针参数是如何传递内存的* * 不要用不要用returnreturn语句返回指向语句返回指向“栈内存栈内存”的指针的指针char*GetString(void)charp=helloworld;returnp;/编译器将提出警告编译器将提出警告voidTest4(void)char*str=NULL;str=GetString();/str的内容是垃圾的内容是垃圾printf(“%sn”,str);8.3.1.7动态内存自动释放动态内存自动释放free函数特点释放指针所指的内存没有销毁指针,故指针地址仍然不变(非NULL)例: char*p=(char*
19、)malloc(100);strcpy(p,“hello”);free(p);/p所指的内存被释放,但是所指的内存被释放,但是p所指的地址仍然不变所指的地址仍然不变if(p!=NULL)strcpy(p,“world”);/出错出错没有起到没有起到防错作用防错作用8.3.1.8杜绝杜绝“野指针野指针”什么是“野指针”不是NULL指针是指向“垃圾”内存的指针 If(p!=NULL)不能起到作用野指针的成因指针变量没有被初始化 指针初始化:char *p = NULL;char *str = (char *) malloc(100);指针p被free或者delete之后,没有置为NULL 8.3.
20、2编程的优化编程的优化为什么需要常量Const与#define的比较常量定义规则循环语句的效率For语句的循环控制变量8.3.2.1为什么用常量为什么用常量常量是一种标识符,它的值在运行期间恒定不变。用 #define来定义常量(称为宏常量),还可以用const来定义常量为什么要常量程序的可读性在程序的很多地方输入同样的数字或字符串,难保不发生书写错误。如果要修改数字或字符串,则会在很多地方改动,既麻烦又容易出错。尽量使用含义直观的常量来表示那些将在程序中多次出现的数字或字符串。例如:#define MAX 100/* C语言的宏常量 */ const int MAX = 100;/ C 语言
21、的const常量const float PI = 3.14159;/ C 语言的const常量8.3.2.2const与与#define的比较的比较两种区别比较const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。常量定义规则需要对外公开的常量放在头文件中,不需要对外公开的常量放在定义文件的头部为便于管理,可以把不同模块的常量集中存放在一个公共的头文件中。如果某一常量与其它常量密切相关,应在定义中包含这种关系,而不应给出一些孤立的值。例如:const
22、float RADIUS = 100;const float DIAMETER = RADIUS * 2;8.3.2.3循环语句的效率循环语句的效率在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少CPU跨切循环层的次数。如果循环体内存在逻辑判断,并且循环次数很大,宜将逻辑判断移到循环体的外面。for(row=0;row100;row+)for(col=0;col5;col+)sum=sum+arowcol;if(condition)for(i=0;iN;i+)DoSomething();elsefor(i=0;iN;i+)DoOtherthing();8.3.
23、2.4for语句的循环控制变量语句的循环控制变量循环控制变量规则不可在for 循环体内修改循环变量,防止for 循环失去控制 建议for语句的循环控制变量的取值采用“半开半闭区间”写法 例子:半开半闭区间 闭区间 for(x=0;xN;x+)for(x=0;x=N;x+);8.3.2.5Pragma预处理预处理其格式一般为: #Pragma Para 其中Para 为参数,下面来看一些常用的参数。 #pragmamessage编译信息输出窗口中输出相应的信息,这对于源代码信息的控制是非常重要的。其使用方法为:#Pragma message(“消息文本”) #pragmaonce只要在头文件的最
24、开始加入这条指令就能够保证头文件被编译一次,这条指令实际上在VC6中就已经有了,但是考虑到兼容性并没有太多的使用它。pragmacomment(.)该指令将一个注释记录放入一个对象文件或可执行文件中。 常用的lib关键字,可以帮我们连入一个库文件。8.3.2.5Pragma预处理预处理#pragmapack()对于想结构体Struct这样的类型,VC采用8字节对齐的方式,如果我们不想使用8字节对齐(在网络变成中经常需要这样),我们可以在结构体前面加上 #pragma pack(1) struct . #pragma pack() #pragmawarning#pragmawarning( di
25、sable : 4507 34; once : 4385; error : 164 ) 等价于: #pragma warning(disable:4507 34) / 不显示4507和34号警告信息 #pragma warning(once:4385) / 4385号警告信息仅报告一次 #pragma warning(error:164) / 把164号警告信息作为一个错误。 阶段小节阶段小节数值与0或者NULL的比较操作内存分配注意细节野指针如何避免内存泄露Const与define数据的差别为什么要const数据本章总结本章总结程序员的态度程序员的态度高质量编程规范高质量编程规范微观上高质量
26、微观上高质量宏观上高质量宏观上高质量主要讲述程序员在编程中的主要讲述程序员在编程中的态度态度讲述了程序编码风格和版式讲述了程序编码风格和版式中具体细节中具体细节深入讲述深入讲述C语言中内存管理语言中内存管理和程序的健壮性和对程序编和程序的健壮性和对程序编码的优化。码的优化。实验实验1实验内容编写strcpy函数-已知strcpy函数的原型是char *strcpy(char *strDest, const char *strSrc);其中strDest是目的字符串,strSrc是源字符串。实验目的实验目的编码的规范以及代码注释;const指针和内存的操作;assert函数错误检测利用;内存的释放和数据的返回值;实验分析实验分析函数注释说明;参数定义和内存申请和释放;assert数据检测和野指针检测实现;数据返回值;