几乎每一本c
创新互联建站长期为成百上千客户提供的网站建设服务,团队从业经验10年,关注不同地域、不同群体,并针对不同对象提供差异化的产品和服务;打造开放共赢平台,与合作伙伴共同营造健康的互联网生态环境。为合作企业提供专业的成都做网站、成都网站设计,合作网站改版等技术服务。拥有10余年丰富建站经验和众多成功案例,为您定制开发。
语言基础的书都讲到了函数递归的问题,但是初学者仍然容易在这个地方犯错误。先看看下面的例子:
void
fun(int
i)
{
if
(i0)
{
fun(i/2);
}
printf("%d\n",i);
}
intmain()
{
fun(10);
return
0;
}
问:输出结果是什么?
这是我上课时,一个学生问我的问题。他不明白为什么输出的结果会是这样:
1
2
5
10
他认为应该输出0。因为当i
小于或等于0
时递归调用结束,然后执行printf
函数打印i
的值。
这就是典型的没明白什么是递归。其实很简单,printf("%d\n",i);语句是fun
函数的一部分,肯定执行一次fun
函数,就要打印一行。怎么可能只打印一次呢?关键就是不明白怎么展开递归函数。展开过程如下:
void
fun(int
i)
{
if
(i0)
{
//fun(i/2);
if(i/20)
{
if(i/40)
{
…
}
printf("%d\n",i/4);
}
printf("%d\n",i/2);
}
printf("%d\n",i);
}
这样一展开,是不是清晰多了?其实递归本身并没有什么难处,关键是其展开过程别弄错了。
二、不使用任何变量编写strlen
函数
看到这里,也许有人会说,strlen
函数这么简单,有什么好讨论的。是的,我相信你能熟练应用这个函数,也相信你能轻易的写出这个函数。但是如果我把要求提高一些呢:
不允许调用库函数,也不允许使用任何全局或局部变量编写intmy_strlen
(char
*strdest);似乎问题就没有那么简单了吧?这个问题曾经在网络上讨论的比较热烈,我几乎是全程“观战”,差点也忍不住手痒了。不过因为我的解决办法在我看到帖子时已经有人提出了,所以作罢。
解决这个问题的办法由好几种,比如嵌套有编语言。因为嵌套汇编一般只在嵌入式底层开发中用到,所以本书就不打算讨论c
语言嵌套汇编的知识了。有兴趣的读者,可以查找相关资料。
也许有的读者想到了用递归函数来解决这个问题。是的,你应该想得到,因为我把这个问题放在讲解函数递归的时候讨论。既然已经有了思路,这个问题就很简单了。代码如下:
intmy_strlen(
const
char*
strdest
)
{
assert(null
!=
strdest);
if
('\0'
==
*strdest)
{
return
0;
}
else
{
return
(1
+
my_strlen(++strdest));
}
}
第一步:用assert
宏做入口校验。
第二步:确定参数传递过来的地址上的内存存储的是否为'\0'。如果是,表明这是一个空字符串,或者是字符串的结束标志。
第三步:如果参数传递过来的地址上的内存不为'\0',则说明这个地址上的内存上存储的是一个字符。既然这个地址上存储了一个字符,那就计数为1,然后将地址加1
个char类型元素的大小,然后再调用函数本身。如此循环,当地址加到字符串的结束标志符'\0'时,递归停止。
当然,同样是利用递归,还有人写出了更加简洁的代码:
intmy_strlen(
const
char*
strdest
)
{
return
*strdest?1+strlen(strdest+1):0;
}
这里很巧妙的利用了问号表达式,但是没有做参数入口校验,同时用*strdest
来代替('\0'==
*strdest)也不是很好。所以,这种写法虽然很简洁,但不符合我们前面所讲的编码规范。可以改写一下:
intmy_strlen(
const
char*
strdest
)
{
assert(null
!=
strdest);
return
('\0'
!=
*strdest)?(1+my_strlen(strdest+1)):0;
}
上面的问题利用函数递归的特性就轻易的搞定了,也就是说每调用一遍my_strlen
函数,其实只判断了一个字节上的内容。但是,如果传入的字符串很长的话,就需要连续多次函数调用,而函数调用的开销比循环来说要大得多,所以,递归的效率很低,递归的深度太大甚至可能出现错误(比如栈溢出)。所以,平时写代码,不到万不得已,尽量不要用递归。即便是要用递归,也要注意递归的层次不要太深,防止出现栈溢出的错误;同时递归的停止条件一定要正确,否则,递归可能没完没了。
递归函数有三点要求:
1,递归的终止点,即递归函数的出口
2,不断的递归调用自身
3,递归函数主体内容,即递归函数需要做的事情
ps:3一般可以放在2的前面或者后面,一般1放最前面。另外,2和3可以根据不同的需要合并,比如,有时候递归函数的主体就是返回调用下层函数所得到的结果。
具体例子如下:
void fun(int n)
{
if(n=0) return; //1 这是递归的终点,即出口
fun(n-1); //2、递归函数自身的调用
coutnendl; //3 递归函数的主体内容
}
2,3合并的情况
int fun(int n)
{
if(n=0) return 0;
return fun(n-1)+fun(n-2); //2 3合并
}
首先是要这个求解的问题,适合用递归方法来进行求解。找到这个递归解法结束递归的条件。递归函数中,首先第一个语句就是如果满足递归条件,就直接返回确定的值,否则返回使用递归方法求解的表达式。