资源描述
#,#,单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,#,2,函数,第,章,2函数第章,1,本章目标,参数的规则,熟悉,EVC,基本控件和类向导的使用,函数内部实现的规则,其他建议,返回值的规则,使用断言,本章目标参数的规则熟悉EVC基本控件和类向导的使用函数内部实,2,2.1,参数的规则,函数是,C,程序的基本功能单元,其重要性不言而喻。函数设计的细微缺点很容易导致该函数被错用,所以函数的功能正确是不够的。,函数接口的两个要素是参数和返回值。,C,语言中,函数的参数和返回值的传递方式有两种:值传递和地址传递。,参数的书写要完整。,不要贪图省事只写参数的类型而省略参数名字。如果函数没有参数,则用,void,填充。,例如:,void SetValue(int width,int height);/,良好的风格,void SetValue(int,int);/,不良的风格,float GetValue(void);/,良好的风格,float GetValue();/,不良的风格,2.1 参数的规则函数是C程序的基本功能单元,其重要性不言,3,2.1,参数的规则,参数命名要恰当,顺序要合理。,例如编写字符串拷贝函数,StringCopy,,它有两个参数。如果把参数名字起为,str1,和,str2,,例如:,void StringCopy(char*str1,char*str2);,那么我们很难搞清楚究竟是把,str1,拷贝到,str2,中,还是刚好倒过来。,可以把参数名字起得更有意义,如叫,strSource,和,strDestination,。这样从名字上就可以看出应该把,strSource,拷贝到,strDestination,。,还有一个问题,这两个参数那一个该在前那一个该在后?参数的顺序要遵循程序员的习惯。一般地,应将目的参数放在前面,源参数放在后面。,2.1 参数的规则参数命名要恰当,顺序要合理。,4,2.1,参数的规则,如果将函数声明为:,void StringCopy(char*strSource,char*strDestination);,别人在使用时可能会不假思索地写成如下形式:,char str20;,StringCopy(str,“,Hello World,”,);/,参数顺序颠倒,指针参数,如果参数是指针,且仅作输入用,则应在类型前加,const,,以防止该指针在函数体内被意外修改。,例如:,void StringCopy(char*strDestination,,,const char*strSource);,2.1 参数的规则如果将函数声明为:,5,2.1,参数的规则,避免函数有太多的参数,参数个数尽量控制在,5,个以内。如果参数太多,在使用时容易将参数类型或顺序搞错。,尽量不要使用类型和数目不确定的参数。,C,标准库函数,printf,是采用不确定参数的典型代表,其原型为:,int printf(const chat*format,argument,);,这种风格的函数在编译时丧失了严格的类型安全检查。,2.1 参数的规则避免函数有太多的参数,参数个数尽量控制在,6,2.2,返回值的规则,不要省略返回值的类型。,C,语言中,凡不加类型说明的函数,一律自动按整型处理。这样做不会有什么好处,却容易被误解为,void,类型。,函数名字与返回值类型在语义上不可冲突。,违反这条规则的典型代表是,C,标准库函数,getchar,。,例如:,char c;,c=getchar();,if(c=EOF),按照,getchar,名字的意思,将变量,c,声明为,char,类型是很自然的事情。但不幸的是,getchar,的确不是,char,类型,而是,int,类型,其原型如下:,int getchar(void);,2.2 返回值的规则不要省略返回值的类型。,7,2.2,返回值的规则,由于,c,是,char,类型,取值范围是,-128,,,127,,如果宏,EOF,的值在,char,的取值范围之外,那么,if,语句将总是失败,这种,“,危险,”,人们一般哪里料得到!导致本例错误的责任并不在用户,是函数,getchar,误导了使用者。,不要将正常值和错误标志混在一起返回。,正常值用输出参数获得,而错误标志用,return,语句返回。,回顾上例,,C,标准库函数的设计者为什么要将,getchar,声明为,int,类型呢?,在正常情况下,,getchar,的确返回单个字符。但如果,getchar,碰到文件结束标志或发生读错误,它必须返回一个标志,EOF,。为了区别于正常的字符,只好将,EOF,定义为负数(通常为负,1,)。因此函数,getchar,就成了,int,类型。,2.2 返回值的规则由于c是char类型,取值范围是-1,8,2.2,返回值的规则,我们在实际工作中,经常会碰到上述令人为难的问题。为了避免出现误解,我们应该将正常值和错误标志分开。即:正常值用输出参数获得,而错误标志用,return,语句返回。,函数,getchar,可以改写成,BOOL GetChar(char*c);,虽然,gechar,比,GetChar,灵活,例如,putchar(getchar();,但是如果,getchar,用错了,它的灵活性又有什么用呢?,有时候函数原本不需要返回值,但为了增加灵活性如支持链式表达,可以附加返回值。,例如字符串拷贝函数,strcpy,的原型:,char*strcpy(char*strDest,,,const char*strSrc);,strcpy,函数将,strSrc,拷贝至输出参数,strDest,中,同时函数的返回值又是,strDest,。这样做并非多此一举,可以获得如下灵活性:,char str20;,int length=strlen(strcpy(str,“,Hello World,”,);,2.2 返回值的规则我们在实际工作中,经常会碰到上述令人为,9,2.3,函数内部实现的规则,不同功能的函数其内部实现各不相同,看起来似乎无法就,“,内部实现,”,达成一致的观点。但根据经验,我们可以在函数体的,“,入口处,”,和,“,出口处,”,从严把关,从而提高函数的质量。,在函数体的,“,入口处,”,,对参数的有效性进行检查。,很多程序错误是由非法参数引起的,我们应该充分理解并正确使用,“,断言,”,(,assert,)来防止此类错误。详见,2.5,节,“,使用断言,”,。,在函数体的,“,出口处,”,,对,return,语句的正确性和效率进行检查。,如果函数有返回值,那么函数的,“,出口处,”,是,return,语句。我们不要轻视,return,语句。如果,return,语句写得不好,函数要么出错,要么效率低下。,2.3 函数内部实现的规则不同功能的函数其,10,2.3,函数内部实现的规则,注意事项如下:,return,语句不可返回指向,“,栈内存,”,的,“,指针,”,或者,“,引用,”,,因为该内存在函数体结束时被自动销毁。,例,2.1,char*Func(void),char str=,“,hello world,”,;/str,的内存位于栈上,return str;/,将导致错误,要搞清楚返回的究竟是,“,值,”,还是,“,指针,”,。,2.3 函数内部实现的规则注意事项如下:,11,2.4,其他建议,函数的功能要单一,不要设计多用途的函数。,函数体的规模要小,尽量控制在,50,行代码之内。,尽量避免函数带有,“,记忆,”,功能。相同的输入应当产生相同的输出。,带有,“,记忆,”,功能的函数,其行为可能是不可预测的,因为它的行为可能取决于某种,“,记忆状态,”,。这样的函数既不易理解又不利于测试和维护。在,C,语言中,函数的,static,局部变量是函数的,“,记忆,”,存储器。建议尽量少用,static,局部变量,除非必需。,不仅要检查输入参数的有效性,还要检查通过其它途径进入函数体内的变量的有效性,例如全局变量、文件句柄等。,用于出错处理的返回值一定要清楚,让使用者不容易忽视或误解错误情况。,2.4 其他建议函数的功能要单一,不要设计多用途的函数。,12,2.5,使用断言,程序一般分为,Debug,版本和,Release,版本,,Debug,版本用于内部调试,,Release,版本发行给用户使用。,断言,assert,是仅在,Debug,版本起作用的宏,它用于检查,“,不应该,”,发生的情况。示例,6-5,是一个内存复制函数。在运行过程中,如果,assert,的参数为假,那么程序就会中止(一般地还会出现提示对话,说明在什么地方引发了,assert,)。,2.5 使用断言程序一般分为Debug版本和Release版,13,2.5,使用断言,例,2.2,:,void *memcpy(void*pvTo,const void*pvFrom,size_t size),/,使用断言,assert(pvTo!=NULL),/,防止改变,pvTo,的地址,byte*pbTo=(byte*)pvTo;,/,防止改变,pvFrom,的地址,byte*pbFrom=(byte*)pvFrom;,while(size-0),*pbTo+=*pbFrom+;,return pvTo;,2.5 使用断言例2.2:,14,2.5,使用断言,为了不在程序的,Debug,版本和,Release,版本引起差别,,assert,不应该产生任何副作用。所以,assert,不是函数,而是宏。,程序员可以把,assert,看成一个在任何系统状态下都可以安全使用的无害测试手段。如果程序在,assert,处终止了,并不是说含有该,assert,的函数有错误,而是调用者出了差错,,assert,可以帮助我们找到发生错误的原因。,如果搞不清楚断言检查的是什么,就很难判断错误是出现在程序中,还是出现在断言中。幸运的是这个问题很好解决,只要加上清晰的注释即可。,2.5 使用断言为了不在程序的Debug版本和Release,15,2.5,使用断言,这本是显而易见的事情,可是很少有程序员这样做。这好比一个人在森林里,看到树上钉着一块,“,危险,”,的大牌子。但危险到底是什么?树要倒?有废井?有野兽?除非告诉人们,“,危险,”,是什么,否则这个警告牌难以起到积极有效的作用。难以理解的断言常常被程序员忽略,甚至被删除。,在函数的入口处,使用断言检查参数的有效性(合法性)。,在编写函数时,要进行反复的考查,并且自问:“我打算做哪些假定?”。一旦确定了的假定,就要使用断言对假定进行检查。,2.5 使用断言这本是显而易见的事情,可是很少有程序员这样做,16,总结,本章的内容是在对函数的使用上进行了详细的分析,并提出了一些有益的建议。,遵循这些建议会使你的程序更加的强健,更加高效,更加专业。,本章的要点需要在平时的练习中多加注意,每当写一个函数时就应该提醒自己有没有遵守这些原则,这样优秀的职业习惯将一直伴随着你。,总结本章的内容是在对函数的使用上进行了详细的分析,并提出了一,17,作业,1.,在实验中验证讲义中的代码,?,2.,尝试编写一个完整的字符串拷贝函数,然后测试函数运行的 状况。,作业1.在实验中验证讲义中的代码?,18,
展开阅读全文