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