一个函数在它的函数体内调用它自身称为递归调用。有递归调用操作的函数被称为递归函数。递归调用可以是直接调用,也可以是间接调用。也可以理解为函数的嵌套调用是函数本身。
例如实现一个求阶乘的函数:
long factorial(intn){ if(n== 1 || n == 0 ) { return1; } else { returnfactorial(n - 1) * n;/*递归调用 */ }}
这个函数是一个递归函数。但是运行该函数将无休止地调用其自身,这当然是不正确的。为了防止递归调用无终止地进行,必须在函数内有终止递归调用的手段。常用的办法是加条件判断,满足某种条件后就不再作递归调用,然后逐层返回。
完整的代码如下:
#includelong factorial(intn){ if(n== 1 || n == 0 ) { return1; } else { returnfactorial(n - 1) * n;/*递归调用 */ }} int main(void){ intn; printf("请输入正整数:\n"); scanf("%d",&n); printf( "%d的阶乘是:%d\n", n,factorial(n)); system("pause"); return0;}
运行结果:
在C语言中任何函数之间不能嵌套定义, 主调函数与被调函数之间相互独立(彼此可以调用)。发生函数调用时,被调函数中保护了主调函数的运行环境和返回地址,使得调用函数的状态可以在被调函数运行返回后完全恢复,而且该状态与被调函数无关。
被调函数运行的代码虽是同一个函数的代码体,但由于调用点,调用时状态,返回点的不同,可以看作是函数的一个副本,与调用函数的代码无关,所以函数的代码是独立的。被调函数运行的栈空间独立于调用函数的栈空间,所以与调用函数之间的数据也是无关的。函数之间靠参数传递和返回值来联系,函数看作为黑盒。这种机制决定了C/C++允许函数递归调用
递归调用分为两种形式:直接递归和间接递归。
直接递归即在函数中出现调用函数本身。
例如,下面的代码求斐波那契数列第n项。 斐波那契数列的第一和第二项是1,后面每一项是前二项之和,即1,1,2,3,5,8,13,...。代码中采用直接递归调用:
long fib(intn){ if( n > 2 ) { return(fib(n - 1) + fib(n - 2) );/*直接递归*/ } else { return1; }}
间接递归调用是指函数中调用了其他函数,而该其他函数却又调用了本函数。例如,下面的代码定义两个函数,它们构成了间接递归调用:
int func1(intx){ if( x <= 0 ) { returnx; } returnfunc2( x );} int func2(inty){ returnfunc1( y - 1 );}
在上面的例子中,func1()函数调用了func2()函数,而func2()函数又调用了func1()函数。由于他们都互相调用,因此又被称为互递归函数
递归的条件有4点:
1、必须有完成函数任务的语句,也就是停止递归的条件。
2、必须有能够确定是否能避免递归调用的测试,比如求阶乘的函数:
long factorial(intn){ if(n== 1 || n == 0 ) { return1; } else { returnfactorial(n - 1) * n;/*递归调用 */ }}
语句" if(n== 1 || n == 0 )"就是—个测试, 如果不满足条件,就不进行递归调用。
3、一个递归调用语句。该递归调用语句的参数应该逐渐逼近不满足条件,以至最后断绝递归。
例如,上面求阶乘代码中,语句“factorial(n - 1)” 便是一个递归调用,参数在渐渐变小,这种发展趋势能使测试"if(val>1)”最终不满足。
4、必须先测试,后递归调用。在递归函数定义中,必须先测试,后递归调用。也就是说,递归调用是有条件的,满足了条件后,才可以递归。否则这个函数就会永远调用下去,直到系统为程序预留的栈空间耗尽程序崩溃为止,这称为无穷递归。
例如,下面的代码是一个无穷递归函数:
int func(intx){ inty,z; z= func(y); returnz;}大多数递归函数都能用非递归函数来代替,这种替代操作叫做消去递归。下面例子的代码求两个整数a,b的最大公约数,分别用递归和非递归函数实现
/*递归实现*/long gcdt(inta,intb){ if( a % b== 0 ) returnb; returngcdl( b, a% b );}/*非递归实现*/long gcd2(inta,intb){ inttemp; while(b!= 0) { temp= a%b; a= b; b= temp; } returna;}递归和循环是等价的,用循环能做的事用递归都能做,反之亦然
对递归的评价:
1、递归的目的是简化程序设计,使程序易读。
2、但递归增加了系统开销。 时间上, 执行调用与返回的额外工作要占用CPU时间。空间上,随着每递归一次,栈内存就多占用一截。
3、相应的非递归函数虽然效率高,但却比较难编程,而且相对来说可读性差。
4、现代程序设计的目标主要是可读性好。随着计算机硬件性能的不断提高,程序在更多的场合优先考虑可读而不是高效,所以,鼓励用递归函数实现程序思想。