九味书屋 > 激情辣文电子书 > [免费下载 c语言深度解剖[1] >

第25部分

[免费下载 c语言深度解剖[1]-第25部分

小说: [免费下载 c语言深度解剖[1] 字数: 每页4000字

按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!



Func结束的时候被释放,所以返回str将导致错误。

【规则6…25】函数的功能要单一,不要设计多用途的函数。微软的Win32API就是违反
本规则的典型,其函数往往因为参数不一样而功能不一,导致很多初学者迷惑。

【规则6…26】函数体的规模要小,尽量控制在80行代码之内。

【建议6…27】相同的输入应当产生相同的输出。尽量避免函数带有“记忆”功能。

带有“记忆”功能的函数,其行为可能是不可预测的,因为它的行为可能取决于某种
“记忆状态“。这样的函数既不易理解又不利于测试和维护。在 
C语言中,函数的 
static
局部变量是函数的“记忆”存储器。建议尽量少用static局部变量,除非必需。

【建议6…28】避免函数有太多的参数,参数个数尽量控制在 
4个或4个以内。如果参数太
多,在使用时容易将参数类型或顺序搞错。微软的Win32API就是违反本规则的典型,
其函数的参数往往七八个甚至十余个。比如一个CreateWindow函数的参数就达11个之
多。

【建议6…29】尽量不要使用类型和数目不确定的参数。 
C标准库函数printf是采用不确定参数的典型代表,其原型为: 
int 
printf(const 
chat 
*format'; 
argument'。);


这种风格的函数在编译时丧失了严格的类型安全检查。

【建议6…30】有时候函数不需要返回值,但为了增加灵活性如支持链式表达,可以附加
返回值。例如字符串拷贝函数strcpy的原型:


char 
*strcpy(char*strDest,const 
char 
*strSrc); 
strcpy函数将strSrc拷贝至输出参数strDest中,同时函数的返回值又是 
strDest。这样做
并非多此一举,可以获得如下灵活性: 


charstr'20'; 
int 
length 
= 
strlen(strcpy(str; 
“Hello 
World”) 
);


【建议6…31】不仅要检查输入参数的有效性,还要检查通过其它途径进入函数体内的变
量的有效性,例如全局变量、文件句柄等。

【规则6…32】函数名与返回值类型在语义上不可冲突。

违反这条规则的典型代表就是C语言标准库函数getchar。几乎没有一部名著没有提到 
getchar函数,因为它实在太经典,太容易让人犯错误了。所以,每一个有经验的作者都
会拿这个例子来警示他的读者,我这里也是如此: 


charc; 
c= 
getchar(); 
if(EOF 
 
c) 
{ 


… 

按照 
getchar名字的意思,应该将变量 
c定义为 
char类型。但是很不幸, 
getchar函数的
返回值却是 
int类型,其原型为: 
intgetchar(void);

由于 
c是 
char类型的,取值范围是 
'…128;127';如果宏 
EOF的值在 
char的取值范围之外, 
EOF的值将无法全部保存到 
c内,会发生截断,将 
EOF值的低 
8位保存到 
c里。这样 
if语
句有可能总是失败。这种潜在的危险,如果不是犯过一次错,肯怕很难发现。

6。4,函数递归
6。4。1,一个简单但易出错的递归例子
几乎每一本 
C语言基础的书都讲到了函数递归的问题,但是初学者仍然容易在这个地
方犯错误。先看看下面的例子: 


voidfun(inti) 


{ 
if 
(i》0) 
{ 


fun(i/2); 
} 
printf(〃%dn〃;i); 


} 


intmain() 



{ 
fun(10); 


return0; 



问:输出结果是什么?

这是我上课时,一个学生问我的问题。他不明白为什么输出的结果会是这样: 


0 


1 


2 


5 


10

他认为应该输出 
0。因为当 
i小于或等于 
0时递归调用结束,然后执行 
printf函数打印 
i的值。

这就是典型的没明白什么是递归。其实很简单,printf(〃%dn〃;i);语句是 
fun函数的一部
分,肯定执行一次 
fun函数,就要打印一行。怎么可能只打印一次呢?关键就是不明白怎么
展开递归函数。展开过程如下: 


voidfun(inti) 


{ 
if 
(i》0) 
{ 


//fun(i/2); 
if(i/2》0) 
{ 


if(i/4》0) 
{ 


… 
} 
printf(〃%dn〃;i/4); 


} 


printf(〃%dn〃;i/2); 
} 
printf(〃%dn〃;i); 




这样一展开,是不是清晰多了?其实递归本身并没有什么难处,关键是其展开过程别弄错了。

6。4。2,不使用任何变量编写 
strlen函数
看到这里,也许有人会说,strlen函数这么简单,有什么好讨论的。是的,我相信你能
熟练应用这个函数,也相信你能轻易的写出这个函数。但是如果我把要求提高一些呢:

不允许调用库函数,也不允许使用任何全局或局部变量编写 
intmy_strlen(char*strDest); 



似乎问题就没有那么简单了吧?这个问题曾经在网络上讨论的比较热烈,我几乎是全
程“观战”,差点也忍不住手痒了。不过因为我的解决办法在我看到帖子时已经有人提出了,
所以作罢。

解决这个问题的办法由好几种,比如嵌套有编语言。因为嵌套汇编一般只在嵌入式底
层开发中用到,所以本书就不打算讨论 
C语言嵌套汇编的知识了。有兴趣的读者,可以查
找相关资料。

也许有的读者想到了用递归函数来解决这个问题。是的,你应该想得到,因为我把这
个问题放在讲解函数递归的时候讨论。既然已经有了思路,这个问题就很简单了。代码如下: 


intmy_strlen(constchar*strDest) 


{ 


assert(NULL!= 
strDest); 


if 
('0' 
 
*strDest) 


{ 


return0; 


} 


else 


{ 


return(1+my_strlen(++strDest)); 


} 




第一步:用 
assert宏做入口校验。

第二步:确定参数传递过来的地址上的内存存储的是否为 
'0'。如果是,表明这是一个
空字符串,或者是字符串的结束标志。

第三步:如果参数传递过来的地址上的内存不为 
'0',则说明这个地址上的内存上存储
的是一个字符。既然这个地址上存储了一个字符,那就计数为 
1,然后将地址加 
1个 
char
类型元素的大小,然后再调用函数本身。如此循环,当地址加到字符串的结束标志符 
'0'时,
递归停止。

当然,同样是利用递归,还有人写出了更加简洁的代码: 


intmy_strlen(constchar*strDest) 


{ 


return*strDest?1+strlen(strDest+1):0; 




这里很巧妙的利用了问号表达式,但是没有做参数入口校验,同时用 
*strDest来代替('0' 
 
*strDest)也不是很好。所以,这种写法虽然很简洁,但不符合我们前面所讲的编码规范。
可以改写一下: 


intmy_strlen(constchar*strDest) 


{ 


assert(NULL!= 
strDest); 


return('0' 
!= 
*strDest)?(1+my_strlen(strDest+1)):0; 




上面的问题利用函数递归的特性就轻易的搞定了,也就是说每调用一遍 
my_strlen函数,
其实只判断了一个字节上的内容。但是,如果传入的字符串很长的话,就需要连续多次函数
调用,而函数调用的开销比循环来说要大得多,所以,递归的效率很低,递归的深度太大甚
至可能出现错误(比如栈溢出)。所以,平时写代码,不到万不得已,尽量不要用递归。即


便是要用递归,也要注意递归的层次不要太深,防止出现栈溢出的错误;同时递归的停止条
件一定要正确,否则,递归可能没完没了。


第七章文件结构

一个工程是往往由多个文件组成。这些文件怎么管理、怎么命名都是非常重要的。下面
给出一些基本的方法,比较好的管理这些文件,避免错误的发生。

7。1,文件内容的一般规则
【规则 
7…1】每个头文件和源文件的头部必须包含文件头部说明和修改记录。
源文件和头文件的头部说明必须包含的内容和次序如下: 


/************************************************************************ 


* 
FileName 
: 
FN_FileName。c/FN_FileName。h 
* 
Copyright 
: 
2003…2008XXXXCorporation;AllRightsReserved。 
* 
ModuleName 
: 
DrawEngine/Display 
* 
* 
CPU 
: 
ARM7 
* 
RTOS 
: 
Tron 
* 
* 
CreateDate 
: 
2008/10/01 
* 
Author/Corporation 
: 
WhoAmI/yourpanyname 
* 
* 
AbstractDescription 
: 
Placesomedescriptionhere。 
* 
*…RevisionHistory… 
No 
Version 
Date 
RevisedBy 
Item 
Description 
* 
1 
V0。95 
08。05。18 
WhoAmI 
abcdefghijklm 
WhatUDo 
* 
************************************************************************/
【规则7…2】各个源文件必须有一个头文件说明,头文件各部分的书写顺序下:

No。 
Item 
1 
Header 
File 
Header 
Section 
2 
Multi…Include…Prevent 
Section 
3 
Debug 
Switch 
Section 
4 
Include 
File 
Section 
5 
Macro 
Define 
Section 
6 
Structure 
Define 
Section 
7 
PrototypeDeclareSection 


其中 
Multi…Include…PreventSection是用来防止头文件被重复包含的。
如下例: 


#ifndef 
__FN_FILENAME_H 
#define__FN_FILENAME_H 
#endif
其中“FN_FILENAME”一般为本头文件名大写,这样可以有效避免重复,因为同一工程
中不可能存在两个同名的头文件。 



/************************************************************************ 


* 
FileName 
: 
FN_FileName。h 
* 
Copyright 
: 
2003…2008XXXXCorporation;AllRightsReserved。 
* 
ModuleName 
: 
DrawEngine/Display 
* 
* 
CPU 
: 
ARM7 
* 
RTOS 
: 
Tron 
* 
* 
CreateDate 
: 
2008/10/01 
* 
Author/Corporation 
: 
WhoAmI/yourpanyname 
* 
* 
AbstractDescription 
: 
Placesomedescriptionhere。 
* 
*RevisionHistory… 
No 
Version 
Date 
RevisedBy 
Item 
Description 
* 
1 
V0。95 
08。05。18 
WhoAmI 
abcdefghijklm 
WhatUDo 
* 
************************************************************************/ 
/************************************************************************ 
* 
Multi…Include…PreventSection 
************************************************************************/ 
#ifndef 
__FN_FILENAME_H 
#define 
__FN_FILENAME_H 
/************************************************************************ 


* 
DebugswitchSection 
***********************************************************************

返回目录 上一页 下一页 回到顶部 0 0

你可能喜欢的