C语言小知识---递归函数的使用

  C语言允许函数自身调用自身,这种调用就被称为递归。好多人刚开始学习递归的时候,往往被一层层嵌套调用搞糊涂了,搞不清楚到底是怎么调用的?现在就通过一个小例子来演示一下,递归调用时,函数是如何运行的。

void up_and_down(int n)
{
	printf("---- Level %d: n location 0x%p \r\n",n,&n);
	if(n<4)
		up_and_down(n + 1);
	printf("**** Level %d: n location 0x%p \r\n",n,&n);
}
int main()
{
	up_and_down(1);
	
	system("pause");
	return 0;
}

首先调用函数给n赋值为1,然后当n小于4时,给n加1,然后继续调用这个函数。直到n的值大于4,就退出函数,运行结果如下:

image.png
从打印结果可以看出n的值为1、2、3、4依次递增,然后又从4、3、2、1依次递减,最后退出程序,那么这个程序执行的流程是什么呢?下面用一张图来分析一下。

递归函数流程分析.png

下面一步一步来分析:

image.png
首先n=1,执行函数体内的第一条打印语句。然后执行if语句,由于n当前值为1,小于4,所以又调用函数本身,将1+1的值传递给函数。

image.png
接下来执行函数体内的第一条打印语句。然后执行if语句,由于n当前值为2,小于4,所以又调用函数本身,将2+1的值传递给函数。

image.png
继续执行函数体内的第一条打印语句。然后执行if语句,由于n当前值为3,小于4,所以又调用函数本身,将3+1的值传递给函数。

image.png
继续执行函数体内的第一条打印语句。然后执行if语句,由于n当前值为4,不小于4,所以if条件不成立,不会再调用up_and_down()函数了,而是执行函数体内最后一条打印语句。当这条打印语句执行完之后,退出函数。 由于第4次条用函数,是从第三次调用函数中来的,所以退出第四次函数后,程序就会回到第三次函数调用它的地方。

image.png

第四次函数退出后,相当于就返回到了第三次函数的up_and_down(3 + 1);这个位置,由于这个函数已经执行完了,所以代码继续执行,打印最后一行的打印语句。

同样当第三次调用的函数执行完成之后,程序就会返回到调用的位置,也就是返回到第二次调用的函数中。

image.png

程序返回到up_and_down(2 + 1);这个语句之后,然后继续执行最后一行的打印语句。当最后一行的打印语句执行完成之后,第二次调用的函数就会返回到调用它的地方,也就是返回到第一次调用函数的内部。

image.png

程序返回到up_and_down(1 + 1);这个语句之后,然后继续执行最后一行的打印语句。当最后一行的打印语句执行完成之后,程序返回到第一次调用这个函数的地方。而第一次调用这个函数是在main()函数中,所以程序就会退回到main()函数中。由于main()函数中没有执行其他语句。所以程序整个流程就运行结束了。

通过打印的结果也可以看出,首先打印的都是函数体内第一个行的打印语句,当4次函数调用都完成后,才继续打印函数体中的最后一行打印语句。通过n的地址也可以看出来,每次新调用函数的时候,都会给n重新申请一个地址,然后当函数退出的时候,再将申请的地址释放掉。

由此可见递归函数在执行的时候,还是比较浪费内存空间的,对于资源很有限的单片机来说,还是尽量少用递归函数。难道递归函数真的一点优势都没有吗?当然是有的,通过对于上面的例子观察可以发现,递归函数执行时是先进后出的原则,那么对于需要倒叙执行的程序来说,使用递归函数是非常方便的。
比如在进行进制转换的时候,第一次计算的值要放到结果的最后一位,最后一次计算的值要放在结果的第一位。

比如现在要将10转换为二进制数。计算过程如下:

10%2 ---- 0

10/2=5

5%2 ----- 1

5/2=2

2%2 ----- 0

2/2=1

1%2 ----- 1

首先将10除以2取余数,余数为0,那么这个0就是二转进制的最低位。接下来将10除以2去整,然后再用结果除以2取余,这个就是二进制的倒数第二位,安装这样的规律,依次计算,直到除数结果为1.

也就是传递的参数变为1时,结束递归的调用。 这样10转换为二进制就时 1010,这个数字刚好是计算的数字倒叙排列。

下面就通过递归来实现这个功能

void to_binary(unsigned long  n)
{
	int r;
	r = n % 2;
	if(n >= 2)
		to_binary(n/2);
	putchar(r == 0?'0':'1');
	return;
}
int main()
{
	to_binary(100);
	printf("\r\n");
	system("pause");
	return 0;
}

定义一个函数来打印二进制的值,首先用这个数字除以2取余,然后将数字除以2取整之后继续调用此函数,运行结果如下:

image.png

将100转换为二进制值
image.png

程序打印的结果和实际计算的结果一样,说明程序功能是正常的。为了方便查看,可以将函数执行过程打印出来。

image.png

为了方便查看函数执行效率,这里可以将函数执行的时间打印出来。
测试代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
void to_binary(unsigned long  n)
{
	int r;
	r = n % 2;
	printf("n-%d,r-%d\r\n",n,r);
	if(n >= 2)
		to_binary(n/2);
	putchar(r == 0?'0':'1');
	return;
}
typedef union _LARGE_INTEGER
{
	struct
	{
		long LowPart ;// 4字节整型数
		long  HighPart;// 4字节整型数
	};
	long long QuadPart;// 8字节整型数
 
} LARGE_INTEGER; 
 
int main(int argc, char *argv[])
{
	int i = 0,val = 0;
	clock_t startTime,endTime;
	int time = 0;
	
	LARGE_INTEGER secondcount= {0};
	LARGE_INTEGER startcount= {0};
	LARGE_INTEGER stopcount= {0};
 
	QueryPerformanceFrequency(&secondcount);     //获取每秒多少CPU Performance Tick  单位us 
	//printf(" 3:系统计数频率为: %d \r\n",secondcount.QuadPart);
 
	QueryPerformanceCounter(&startcount);		//计时开始
	to_binary(100);					//递归调用 
	QueryPerformanceCounter(&stopcount);		//计时结束
	
	time=( ((stopcount.QuadPart - startcount.QuadPart)*1000*1000)/secondcount.QuadPart);
	printf(" \r\n\r\n  程序运行时间为: %d us\r\n\r\n",time);
 
	system("pause");
	return 0;
}

这里添加了一个测试递归调用函数执行时间,测试的原理就是,在函数执行前记录一下系统的计数值,然后执行完成之后,在记录一下系统计数值,这两个值的差就是系统的执行时间。
执行结果如下:

image.png
可以看到计算一次100的二进制数需要1.163ms,这对于计算机来说太慢了。如果计算一个更大的数,那岂不是更费时间,用数字100000000测试一下。

image.png

花费的时间为5.1ms左右,可见数字越大函数嵌套的就越深,就会越费内存。执行时间也就越久。

以后再使用递归函数的时候,一定要注意使用环境,否则如果在一个资源比较少的单片机上,调用了一个嵌套比较深的递归函数,很可能计算到一半系统资源就会被耗尽,导致程序崩溃,而且很难查找到原因。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值