递归在C语言中的工作原理
我是C语言的新手,我正在阅读有关递归的书籍,但我完全搞不懂 我感到困惑的主要部分是,当达到退出条件时,事情是如何得到放松的。我想知道在递归过程中,值是如何从堆栈中推送和弹出的 还有谁能给我一个递归的诊断视图吗递归在C语言中的工作原理,c,recursion,C,Recursion,我是C语言的新手,我正在阅读有关递归的书籍,但我完全搞不懂 我感到困惑的主要部分是,当达到退出条件时,事情是如何得到放松的。我想知道在递归过程中,值是如何从堆栈中推送和弹出的 还有谁能给我一个递归的诊断视图吗 谢谢…让我们假设一个函数: int MyFunc(int counter) { // check this functions counter value from the stack (most recent push) // if counter is 0, we'v
谢谢…让我们假设一个函数:
int MyFunc(int counter) {
// check this functions counter value from the stack (most recent push)
// if counter is 0, we've reached the terminating condition, return it
if(counter == 0) {
return counter;
}
else {
// terminating condition not reached, push (counter-1) onto stack and recurse
int valueToPrint = MyFunc(counter - 1);
// print out the value returned by the recursive call
printf("%d", valueToPrint);
// return the value that was supplied to use
// (usually done via a register I think)
return counter;
}
}
int main() {
// Push 9 onto the stack, we don't care about the return value...
MyFunc(9);
}
输出为:012345678
第一次通过MyFunc
,计数为9。它没有通过终止检查(它不是0),因此调用递归调用,使用(计数器-1)
,8
这会重复,每次递减推送到堆栈上的值,直到计数器==0
。在这一点上,终止子句激发,函数只返回计数器(0)的值,通常在寄存器中
下一次调用堆栈时,使用返回的值打印(0),然后返回调用堆栈时提供给堆栈的值(1)。这重复了:
下一次调用堆栈时,使用返回的值打印(1),然后返回调用时提供给堆栈的值(2)。等等,直到你到达栈顶
因此,如果用3调用了MyFunc
,那么您将得到等价物(忽略堆栈中的返回地址等):
在C语言中,递归就像普通的函数调用一样
因此,对于递归,步骤1和2执行几次,然后可能执行3次(可能只执行一次),最后执行4和5次(与1和2的次数相同) 另一个答案是,一般来说你不知道。作为一种语言,C没有任何堆。编译器使用一个名为堆栈的内存位置来存储控制流信息,如堆栈帧、返回地址和寄存器,但C语言中没有禁止编译器将这些信息存储在其他地方的内容。对于实践方面,前面的答案是正确的。这就是C编译器今天的工作方式 当达到退出条件时,事情是如何展开的? 首先,关于递归的几句话:a用于复杂任务,可以逐渐分解并简化为初始任务的简单实例,直到达到允许直接计算的形式(基本情况)。这是一个与政治密切相关的概念 更具体地说,递归函数直接或间接地调用自身。在直接递归函数中,
foo()。在间接递归中,函数foo()
例子:
阶乘n,表示为n!,是从1到n的正整数的乘积。对于n>0,阶乘可以正式定义为:
阶乘(0)=1,(基本情况)
阶乘(n)=n*阶乘(n-1)。(递归调用)
递归出现在这个定义中,因为我们用阶乘(n-1)定义factorial(n)
每个递归函数都应该有终止条件来结束递归。在本例中,当n=0时,递归停止。用C表示的上述函数为:
这个例子是一个直接递归的例子
这是如何实现的?在软件层面,它的实现与实现其他功能(过程)没有区别。一旦您了解到每个过程调用实例都不同于其他实例,递归函数调用本身的事实就不会有太大的区别
每个活动过程都维护一个存储在堆栈上的。激活记录由参数、返回地址(调用方的)和局部变量组成
激活记录在调用过程时出现,在过程终止并将结果返回给调用方后消失。因此,对于每个未终止的过程,存储包含该过程状态的激活记录。激活记录的数量,以及运行程序所需的堆栈空间量,取决于递归的深度
还有谁能给我一个递归的诊断视图吗?
下图显示了阶乘(3)的激活记录:
从图中可以看出,对阶乘的每次调用都会创建一个激活记录,直到达到基本情况为止,从那里开始,我们以乘积的形式累积结果
这个问题已经得到了广泛的回答。请允许我用一种更具教育性的方法来补充一个答案
你可以把函数递归看作是一堆泡泡,有两个不同的阶段:推进阶段和爆破阶段
A) 推进阶段(或OP称之为“推进堆栈”)
0)启动气泡#0是主要功能。它被以下信息放大:
- 局部变量
- 对下一个气泡#1的调用(对递归函数的第一个调用)
函数,MYFUNC)
1) 气泡#1在轮到它的时候会被以下信息炸毁:
- 上一个气泡的参数#0
- 局部变量(如有必要)
- 回信地址
- 使用返回值终止检查(例如:if(counter==0){return 1})
- 呼唤下一个泡沫#2
Call MyFunc(3) Stack: [3]
Call MyFunc(2) Stack: [2,3]
Call MyFunc(1) Stack: [1,2,3]
Call MyFunc(0) Stack: [0,1,2,3]
Termination fires (top of stack == 0), return top of stack(0).
// Flow returns to:
MyFunc(1) Stack: [1,2,3]
Print returned value (0)
return current top of stack (1)
// Flow returns to:
MyFunc(2) Stack: [2,3]
Print returned value (1)
return current top of stack (2)
// Flow returns to:
MyFunc(3) Stack: [3]
Print returned value (2)
return current top of stack (3)
// and you're done...
int fact(int n){
if(n == 0){
return 1;
}
return (n * fact(n-1));
}
/*Bubble #3 (MYFUNC recursive function): Parameters from Bubble #2,
local variables, returning address, terminating check (NAIL),
call (not used here, as terminating check is positive).*/