首页 > 编程知识 正文

c语言递归是什么意思,C语言尾递归

时间:2023-05-04 23:15:19 阅读:153355 作者:4268

在计算机科学领域,递归公式是通过递归函数实现的。 程序调用自己的编程技巧称为递归(recursion )。

过程或函数具有在其定义或描述中直接或间接调用自身的方式,通常将大的复杂问题转换成与原始问题相似的小问题并求解。 递归策略只需要几个程序就可以表示求解问题过程所需的重复计算,大大减少了程序的代码量。 递归的能力在于用有限的语句定义对象的无限集合。

通常,递归需要边界条件、递归前进段和递归返回段。

如果不满足边界条件,则递归前进; 当满足边界条件时,递归返回。

注意:

)递归是指在过程或函数中调用自身。

)2)使用递归策略时,需要明确的递归终止条件,称为递归出口。

基本递归

问题:计算n!

数学公式是n!=n(n-1 ) ) n-2 ) . 21

使用递归方法,可以定义如下:

交流c有兴趣一起学习语言的伙伴可以分组: 941636044

递归计算4!

f(4)=4F(3)3)递归阶段

f(3)=3F(2)2)

f(2)=2F(1)1)

f(1)=1结束条件

f(2) f(2) )1)回归阶段

f(3)=(3)(2) ) ) )。

f(4)=(4)(6) ) ) )。

24? 递归完成

递归实现阶乘函数的实现: intfact(intn ) )。

? 是if(n0 )

? 返回0;

? elseif(n==0||n==1) ) ) )

? 返回1;

? else

? returnn*fact(n-1 );

}

让我们详细分析一下递归的工作原理

首先,我们来看一下函数在c语言中的执行方式。 需要了解C程序在内存中的组织方式。

BSS段3360(BSSsegment )通常是指用于存储程序中未初始化的全局变量的内存区域。 BSS是英语Block Started by Symbol的简称。 BSS段是静态内存分配。

数据段:数据段通常是指用于存储程序中初始化的全局变量的内存区域。 数据段是静态内存分配。

代码段:代码段(code segment/text segment )通常是否意味着要存储? 程序执行代码的存储器区域。 该区域的大小在程序运行之前就已经确定了,内存区域通常是只读的吗? 中选择所需的族。 某些架构还允许代码段是可写的,也就是说,允许修改程序。 代码段也可能包含只读常量变量吗? 例如,字符串常数等。 程序段是内存中程序代码的映射。 一个程序在内存中可以有多个副本。

堆(heap )堆用于存储进程运行时动态分配的内存段,大小不是固定的,可以动态扩展或收缩。 当进程调用malloc/free等函数分配内存时,新分配的内存会动态添加到堆中((堆扩展) /释放的内存会从堆中删除) )。

堆栈(stack ) :堆栈也称为堆栈,存储程序的局部变量(但不包含静态公告的变量)? 静态? 什么意思? 在数据段中存储变量)。 此外,调用函数时,栈用于传递参数和返回值。 堆栈的特征是后进先出,所以堆栈对于保存/恢复调用现场特别有用。 在这种意义上,可以将堆栈视为存储和交换临时数据的内存区域。

堆的增长方向从低地址到高地址向上增长,而堆栈的增长方向正好相反(实际情况与CPU的体系结构相关)。

当在c程序中调用函数时,栈将分配空间以存储有关该调用的信息,并且每个调用都被视为活动的。 堆栈上的存储空间称为活动记录或堆栈帧

堆栈框架由五个区域组成:输入参数、返回区域、用于计算表达式的临时存储区域、调用函数时保存的状态信息和输出参数。 请参照下图。

有兴趣一起交流学习c/c的朋友可以加群: 941636044

可以在以下步骤中确认:#include

int g1=0,g2=0,g3=0;

内部(inti ) )。

{

? int m1=0,m2,m3=0,*p_max;

? static n1_max=0,n2_max,n3_max=0;

? p_max=(int* ) malloc ) ) 10;

? printf (打印最大程序地址n );

? 打印(inmax :0 xxnn )、max );

? printf (打印最大传入参数地址n );

? printf(inmax:0xxnn )、I );

? printf (打印max函数中静态

态变量地址n");

????printf("0x%08xn",&n1_max); //打印各本地变量的内存地址

????printf("0x%08xn",&n2_max);

????printf("0x%08xnn",&n3_max);

????printf("打印max函数中局部变量地址n");

????printf("0x%08xn",&m1); //打印各本地变量的内存地址

????printf("0x%08xn",&m2);

????printf("0x%08xnn",&m3);

????printf("打印max函数中malloc分配地址n");

????printf("0x%08xnn",p_max); //打印各本地变量的内存地址

????if(i) return 1;

????else return 0;

}

int main(int argc, char **argv)

{

????static int s1=0, s2, s3=0;

????int v1=0, v2, v3=0;

????int *p;????

????p = (int*)malloc(10);

????printf("打印各全局变量(已初始化)的内存地址n");

????printf("0x%08xn",&g1); //打印各全局变量的内存地址

????printf("0x%08xn",&g2);

????printf("0x%08xnn",&g3);

????printf("======================n");

????printf("打印程序初始程序main地址n");

????printf("main: 0x%08xnn", main);

????printf("打印主参地址n");

????printf("argv: 0x%08xnn",argv);

????printf("打印各静态变量的内存地址n");

????printf("0x%08xn",&s1); //打印各静态变量的内存地址

????printf("0x%08xn",&s2);

????printf("0x%08xnn",&s3);

????printf("打印各局部变量的内存地址n");

????printf("0x%08xn",&v1); //打印各本地变量的内存地址

????printf("0x%08xn",&v2);

????printf("0x%08xnn",&v3);

????printf("打印malloc分配的堆地址n");

????printf("malloc: 0x%08xnn",p);

????printf("======================n");

????max(v1);

????printf("======================n");

????printf("打印子函数起始地址n");

????printf("max: 0x%08xnn",max);

????return 0;

}

栈是用来存储函数调用信息的绝好方案,然而栈也有少量缺点:

栈维护了每个函数调用的信息直到函数返回后才释放,这需要占用相当大的空间,尤其是在程序中使用了许多的递归调用的情况下。除此之外,由于有大量的信息需要保存和恢复,因而生成和销毁活跃记录需要消耗肯定的时间。我们需要考虑采用迭代的方案。幸运的是我们可以采用一种称为尾递归的特殊递归方式来避免前面提到的这些缺点。

尾递归

定义

假如一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的。当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。尾递归函数的特点是在回归过程中不用做任何操作,这个特性很重要,由于大多数现代的编译器会利用这种特点自动生成优化的代码。

原理

当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活动记录而不是在栈中去创立一个新的。编译器可以做到这点,由于递归调用是当前活跃期内最后一条待执行的语句,于是当这个调用返回时栈帧中并没有其余事情可做,因而也就没有保存栈帧的必要了。通过覆盖当前的栈帧而不是在其之上重新增一个,这样所使用的栈空间就大大缩减了,这使得实际的运行效率会变得更高。尽管编译器能够优化尾递归造成的栈溢出问题,但是在编程中,我们还是应该尽量避免尾递归的出现,由于所有的尾递归都是可以用简单的goto循环替代的。

实例

为了了解尾递归是如何工作的,让我们再次以递归的形式计算阶乘。首先,这可以很容易让我们了解为什么之前所定义的递归不是尾递归。回忆之前对计算n!的定义:在每个活跃期计算n倍的(n-1)!的值,让n=n-1并持续这个过程直到n=1为止。这种定义不是尾递归的,由于每个活跃期的返回值都依赖于用n乘以下一个活跃期的返回值,因而每次调用产生的栈帧将不得不保存在栈上直到下一个子调用的返回值确定。现在让我们考虑以尾递归的形式来定义计算n!的过程。

这种定义还需要接受第二个参数a,除此之外并没有太大区别。a(初始化为1)维护递归层次的深度。这就让我们避免了每次还需要将返回值再乘以n。然而,在每次递归调用中,令a=na并且n=n-1。继续递归调用,直到n=1,这满足结束条件,此时直接返回a就可。

代码实例给出了一个C函数facttail,它接受一个整数n并以尾递归的形式计算n!。这个函数还接受一个参数a,a的初始值为1。facttail使用a来维护递归层次的深度,除此之外它和fact很类似。读者可以注意一下函数的具体实现和尾递归定义的类似之处。int facttail(int n, int a)

{

????if (n < 0)

????????return 0;

????else if (n == 0)

????????return 1;

????else if (n == 1)

????????return a;

????else

????????return facttail(n - 1, n * a);

}

示例中的函数是尾递归的,由于对facttail的单次递归调用是函数返回前最后执行的一条语句。在facttail中碰巧最后一条语句也是对facttail的调用,但这并不是必须的。换句话说,在递归调用之后还可以有其余的语句执行,只是它们只能在递归调用没有执行时才可以执行。

尾递归是极其重要的,不用尾递归,函数的堆栈耗用难以估量,需要保存很多中间函数的堆栈。比方f(n, sum) = f(n-1) + value(n) + sum; 会保存n个函数调用堆栈,而使用尾递归f(n, sum) = f(n-1, sum+value(n)); 这样则只保留后一个函数堆栈就可,之前的可优化删去。

也许在C语言中有很多的特例,但编程语言不只有C语言,在函数式语言Erlang中(亦是栈语言),假如想要保持语言的高并发特性,就必需用尾递归来替代传统的递归。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。