不知道大家学C语言最难的是什么。 反正我初学C语言的时候,觉得最难的就是“递归”,比指针更难理解(C语言中的指针,很多人认为很难理解)。
那么什么是“递归”呢? 有一天翻字典的时候,看到字典是这样解释一个词的:
惊人:形容惊人的形容词。
这要么是恶搞,要么是开玩笑。 但是,数学中确实有很多概念是自己定义的。 比如:n的阶乘等于n乘以n-1的阶乘,0的阶乘等于1。乍一看好像没有说清楚什么是阶乘,但是这样的描述是足以让人们知道如何计算阶乘。 例如计算 4 的阶乘:
4! = 4 x 3! = 4 x 3 x 2! = 4 x 3 x 2 x 1! = 4 x 3 x 2 x 1 x 0! = 4 x 3 x 2 x 1 x 1 = 24
什么是阶乘不需要研究,只需要根据定义计算即可。 当然,这个定义必须有一个“基本条件”。 比如阶乘的“基本条件”是0! = 1. 如果没有“基本条件”,阶乘只会无限向下推,没有尽头。
C语言中的递归函数是什么
说了半天阶乘,为“递归”做铺垫。 如果一个概念需要使用它自己,我们称它的定义为递归的。 很明显,递归函数必须调用自己的函数,所以有点假,我们来看例子,下面用C语言计算n的阶乘。 我们已经知道递归最重要的是“基本条件”。 先写下阶乘的基本条件:
int factorial(int n) { if(0 == n) return 1; }
上面代码实现了0的阶乘等于1,如果n大于0怎么办? 根据阶乘的定义,应该是nx(n-1),代码实现为:
int factorial(int n) { if(0 == n) return 1; else return n*factorial(n-1); }
这就实现了用C语言计算n的阶乘。 该函数调用自身,因此它是一个递归函数。 其实不仅是直接调用自己,间接调用自己也是递归函数。 比如A调用函数B,函数B调用A,那么A也是递归函数。
那么,递归函数是如何执行的呢?
int factorial(int n) { if (n == 0) return 1; else { int recurse = factorial(n-1); int result = n * recurse; return result; } }
这里以(3)为例,调用用实线箭头表示,返回用虚线箭头表示,右边的方框表示调用和返回过程中各函数调用局部变量的变化.
我们看一下图中右边代表存储空间的方框的变化过程。 随着函数调用的深入,一端的存储空间逐渐变大,然后随着函数逐层退出,这一端的存储空间逐渐变小。 这是一个具有特定属性的数据结构。 它的特点是只能在一端增长或缩短,每次访问参数和局部变量时,只能访问这一端的单元,不能访问内部单元。 例如,当(2)的存储空间在末尾时,只能访问它的参数和局部变量,而不能访问(3)和main()的参数和局部变量。
这种性质的数据结构称为栈或堆栈(Stack)。 每次函数调用的参数和局部变量的存储空间(图中的一个小方框)称为栈帧(Stack frame)。 系统为每个程序的运行预留了一个栈空间。 当一个函数被调用时,在这个栈空间中分配一个栈帧,当函数返回时,释放栈帧。
在我之前的文章《C语言入门5,一篇彻底搞懂函数形参和实参,再也不晕了》中也用到了“栈帧”的概念。
可见,用C语言写一个递归函数,最重要的是定义好“基本条件”,否则函数将永远被调用,直到系统资源耗尽,程序崩溃。 递归和循环是等价的,循环能做的事递归也能做,反之亦然。 其实有些编程语言(比如某些LISP)只有递归没有循环。
计算机硬件所能做的只是数据访问、计算、测试和分支、循环(或递归),用高级语言编写的程序运行在计算机上当然不可能做更多的事情,虽然高级语言丰富。 语法特征,但只是为做这些事情提供一些便利。 那么,为什么计算机要这样设计呢? 为什么您认为计算机需要具备这些功能,而不是更多或更少? 这些都归功于早期的计算机科学家,比如艾伦,在计算机尚未诞生的时代,他从数学理论为计算机设计指明了方向。
欢迎在评论区一起讨论和提问。 文章均为手写原创,每天用最浅显的方式介绍C语言。 如果喜欢我的文章,请关注。 您可以看到最新的更新和以前的文章。 本文部分内容涉及linux C编程一站式学习。