题记:
本篇文章由一个“有问题”的程序,带入你进入递归的世界。虽然只是采用了递归中最简单的类型,尾递归。但却能使你马上感受到递归的妙用和烦恼。希望大家由浅入深、循序渐进,更好的使用递归。其中又涉及了递减运算符的使用,它同样是让你欢喜让你忧!虽然使用它给你带来高效的代码,但不小心同样让你深陷泥潭。
1. #include <stdio.h>
2. long fun(int );
3. int i=1,j=3; //i,j仅仅是为了说明递归时是第几级调用
4.
5. int main(void)
6. {
7. int n=3; //测试数据较小,为了能说明问题,大了也无妨(勿溢出)
8. printf ("3!/n");
9. printf ("/nn=%d/n",fun(n));
10. return 0;
11. }
12.
13. long fun(int m)
14. {
15. long ans;
16.
17. if (m>0)
18. {
19.
20. printf ("Level:%d m=%d,m:location %p/n",i++,m,&m);
21.
22. //static int *p=&m; //用于说明后缀递减运算时的问题
23. //printf ("%d/n/n",*p); //用于说明后缀递减运算时的问题
24.
25. ans=fun(m-1)*m; //测试行
26.
27. //1.Right:ans=fun(m-1)*m;
28.
29. //2.Error:ans=fun(m=m-1)*m;
30. //3.Error:ans=m*fun(m=m-1);
31. //4.Error:ans=fun(--m)*m;
32.
33. //5.Error:ans=fun(m--)*m;
34.
35. printf ("Return: Level:%d m=%d,m:location %p/n",j--,m,&m);
36.
37. }
38. else
39. {
40. ans=1;
41. }
42. return ans;
43. }
尝试分析5个可测选项会输出什么结果?
#1正确,依次调用、返回。最终效果为:fun(0)*5*4*3*2*1
#2/3/4表达式虽略有差异,但副作用相同,动作发生时间相同。
貌似可以产生正确结果但运行却发现“n=0”,Why?发生了什么?这就要从递归的原理考虑,并对比#1的形式。如果是#1,在一级调用fun时(main对fun的调用),即fun(3),然后这个函数调用还没执行完,就执行了fun(2) 『自身调用,fun对fun,和#1一样,但同时它把m的值改变了!』… 效果如上文所示
如果采用#2的形式,效果:fun(0)*4*3*2*1*0,所以“n=0”
#5是最复杂的,同时难于理解,最好能了解递归的细节、副操作、顺序点等知识(在文章最后,有简略解释)。
一级调用fun(),即fun(3),注意传递给fun函数时,m就是3,而二级调用(调用自身)时,m的值还没改变呢(后缀递减)!又是fun(3)。三级调用也将是fun(3)…… 递归虽然有条件检测语句(17行)用于结束递归,但m值相当于不变,一直递归,知道资源耗竭为止!
是否有疑问呢?为什么要说“相当于”呢?如果你够细心,肯定发现了一点蛛丝马迹!
m值其实变了,但什么时候变了?以二级调用为例解释,即第一次自身调用时。fun函数调用时m给了它,随后m变成了,这可以通过加入22、23行代码加以验证,你会发现m确实变成了2。那三级调用不就是fun(2)了吗?不是!!
递归是个很奇妙的东西!它总能迷惑你的双眼。
每一级函数调用都有自己的变量!此m非彼m,可以从地址中发现(我在这个实例已经加入了显示地址的语句)。
如果两个不同的函数,可能我们很容易的就明白,两个m的不同
如:
fun1(int m)
{
m+=3;
printf (“%d”,m);
}
fun2(int m)
{
printf (“%d”,m);
}
但换到递归,我们可能就摸不到头绪了。在此建议大家:多联系多思考,语言是由基本要素扩充扩展而来。如递归不过是函数调用自身,那么函数具有的特性,它也应该具有!
递减运算符在这个程序给你带来的trouble够大吗?所以用之勿慎!c就是这样,够灵活以致可以够高效、够强大;灵活又使我们摔倒千次。
决定命运的关键:
立即行动