C语言里最难的就是递归调用了。可能有好多人会认为指针比较难,其实不是如此,指针只不过是C语言的一个语法概念,但是想理解递归却需要非常多的知识,下面咱们就来看看是什么导致了递归调用那么难。
我给递归调用的理解分三个阶段。
1.透彻理解函数传参
2.理解运行栈的机制
3.把递归调用当成树来理解
我带过的好多学生在学完C语言之后对函数传参都不怎么理解,也就是说大部分学生在大一大二甚至大三的时候都不怎么理解函数传参。
至于运行栈,那跟不用说了,大部分学生都没听过这个概念。
这两点都可以通过学习汇编语言来提高。
至于第三点又需要一点数据结构树的概念,因此可以看出,想要完全理解递归是需要额外花费很多时间的。
那么这篇文章就简单讲解一下这三点,希望对大家有所帮助。
第一点,C语言里有值传递和指针传递两种参数传递方法,但本质上是相同的,即他们都是值传递。那么既然只有一种传递,如何能够学的透彻呢,这就必须靠调试来帮忙了,通过反复的看内存,慢慢的建立起一种概念,自然会理解为什么只有值传递。
下面以两个例子稍作说明:
void swap(int a, int b)
{
int tmp;
tmp = a;
a = b;
b = tmp;
},
int main()
{
int a = 3;
int b = 4;
swap(a,b);
return 0;
}
这是研究值传递的一个极好的例子,为了理解的透彻,咱们说下运行栈,理解起来可以这样认为,运行栈是栈,是函数调用时将整个函数压栈和出栈的一种机制。
记住一点,参数属于主调函数的栈,那么如何区分哪里是主调函数的栈和被调函数的栈呢,很容易,就看汇编代码里面哪里出现call 指令哪里就是分界线。
先看看运行栈的大致样子
再看看实际的内存中是什么样子的。
这里有一句话,大家一定要记住:形参是实参的拷贝或者叫副本。
从上图中可以看出,实参和形参的值是一样的但是所处的位置不同,而swap函数交换的是形参的值,和实参没有关系的。
如果是指针传递呢
void swap(int *p, int *q)
{
int tmp;
tmp = *p;
*p = *q;
*q = tmp;
}
int main()
{
int a = 3;
int b = 4;
swap(&a,&b);
return 0;
}
大家可以做个试验,这时传递的是a和b的地址,也就是说入栈的是a和b的地址。那么形参拿着地址就能取得主函数中a和b的值,进行交换自然也就能交换成功。
第二点在第一点里基本上都说清楚了。
说下第三点:有些情况下把递归当做树来理解是个不错的选择。
int fn(int n)
{
if(n == 1)
return 1;
else
return n*fn(n-1);
}
int main()
{
int b = fn(4);
return 0;
}
这个可以理解成一颗下图的树:
再看一个例子
int fn(int n)
{
if (n <= 1)
return n;
else
return fn(n-1) + fn(n-2);
}
int main()
{
int b = fn(4);
return 0;
}
这一个的图可以画成这样
也就是说递归调用中调用自己一次那么就是一个单节点的树,调用自己两次,每个节点就有两个分支,调用自己三次,每个节点就有三个分支。
当然过程是一个后续遍历树(不确定,我也搞不清楚),但是如果能把后续遍历直接看成层次遍历,我想你理解递归就容易的多了。
愿大家早早脱离递归的苦海。。。。