函数的调用10-29日(第九周周一)

1.一个奇怪的函数调用程序

  • 问题:如何完成两个数据的交换?
    分析:同学A和同学B如何交换双方座位?都会轻功的话,双方不用着地且同时朝对方的座位飞过去即可。但我们都是凡人,怎么办?

通过细心思考,聪明的我们发现通过以下步骤即可:

  • 步骤1:同学A先离开自己的座位,找个其他地方先待着;
  • 步骤2:同学B离开自己的座位,去坐在A的座位上;
  • 步骤3:同学A离开待着的地方,去坐在B的座位上。
    通过这三步,双方完成座位的交换。同理,也可以先让B同学离开座位,效果是一样的。

1.1 具体到程序中,如何编程实现两个数据的交换?

在这里插入图片描述

1.2 单用main来实现交换

如果单采用一个main函数来实现交换的话,代码如下:

#include<stdio.h>
int main()
{
	int i,j,temp;
	scanf("%d%d",&i,&j);
	temp=i;
	i=j;
	j=temp;
	printf("i=%d,j=%d\n",i,j);
	return 0;
}

i和j顺利完成了交换。

1.3 采用子函数来实现交换

如果想自己定义一个子函数,来实现交换功能,该子函数则在main函数中调用。具体代码如下:

#include<stdio.h>
void swap(int a,int b) 
{
	int temp;
	temp=a;
	a=b;
	b=temp;
}
int main()
{
	int i,j;
	scanf("%d%d",&i,&j);
    swap(i,j);
	printf("after:i=%d,j=%d\n",i,j);
	return 0;
}

分析:主函数main中通过swap(i,j)对子函数swap进行了调用,i把它的值传给了swap的a,j把它的值传给了swap的b,通过称i和j为实参,称a和b为形参。然后,在swap中完成了a和b的交换。接下来,执行输出语句printf的时候会发生什么呢?为什么呢?该如何修改程序呢?
出现问题的主要原因是:局部变量(局部变量的作用域和生存期引发的)。
既然有局部变量了,就会有全局变量,二者有和区别?同学们可以自行思考下。
修改该程序有两个方法:(1)指针;(2)C++里面的引用。
感兴趣的可查阅资料,这部分内容上课讲述。
此外,编写好一个子函数,其目的就是让调用它,因为子函数被调用了,它的代码才能被执行,函数功能才能得以体现。函数的调用方法有很多,较为重要的有:嵌套调用和递归调用。

2. 函数的嵌套调用

C语言中,函数的定义是独立的,即一个函数不能定义在另一个函数内部。但在调用时,可以在一个函数中调用另一个函数,即函数1调用了函数2,而函数2又调用了函数3,这就是函数的嵌套调用。
实例:输出四个数据中的最大者。
假如如下设计程序:

#include<stdio.h>
int max2(int a,int b)
{
	if(a>b)
		return a;
	else
		return b;
}
int max4(int a,int b,int c,int d)
{
	int res;
	res=max2(a,b);
	res=max2(res,c);
	res=max2(res,d);
	return res;
}
int main()
{
	int a,b,c,d,max;
	scanf("%d%d%d%d",&a,&b,&c,&d);
  max=max4(a,b,c,d);
	printf("max=%d\n",max);
	return 0;
}

分析:
该程序中,max2计算两个数据中的最大者,max4计算四个数据中的最大者,在max4中对max2进行了调用。这就是嵌套调用。
该程序执行的流程为:
步骤1:执行main函数的开头部分;
步骤2:遇到main函数中的max=max4(a,b,c,d);时,中断main函数的执行,转到max4函数中执行;
步骤3:执行max4函数的开头部分,遇到res=max2(a,b);时,中断max4的执行,转到max2中执行;
步骤4:执行max2函数,如果该函数中没有嵌套其他函数,则完成该函数的全部操作,然后返回到max4函数中刚才调用max2时中断的位置;
步骤5:继续按照上述思想执行max4函数中尚未执行的部分;直到max4函数结束;
步骤6:当max4函数执行结束后,返回到main函数中刚才调用max4时中断的位置;
步骤7:继续按照上述思想执行main函数中尚未执行的部分,直到main函数结束。

  • **拓展:函数调用时最多可以嵌套多少层?
  • 答曰:直观上感觉,可以无限层。很遗憾,函数可以嵌套调用多少层是由程序运行时一个名为“栈”的数据结构决定的。一般而言,Windows上程序的默认栈大小约为1Mbit,每一次函数调用至少占用8字节,因此粗略计算,函数调用大概只能嵌套约一千层,如果嵌套调用的函数里包含许多变量和参数,实际值要远远小于这个数目。
    当然,单纯手动书写代码写出一千层嵌套函数调用基本是不可能的,但是通过“递归”的方法就可以轻松达到这个上限。**

3. 函数的递归调用

前面计算1+2+3+…+n的方法:用循环实现逐次相加。其实,这类问题也可以通过函数的递归调用来实现。
什么是函数的递归调用?也即一个函数间接或直接调用自身的过程。递归算法的实质是将原有问题分解为新的问题,而解决新问题时又用到了原有问题的解法。按照这一原则分解下去,每次出现的新问题都是原有问题的简化的子集,而最终分解出来的问题,是一个已知的问题,这便是有限的递归调用。
递归的过程有两个阶段:
第一阶段:递推。将原问题不断分解为新的子问题,逐渐从未知向已知推进,最终达到已知的条件,即递归结束的条件,这时递推阶段结束。
例如,求1+2+3+4+5,可以这样分解:
1+2+3+4+5=(1+2+3+4)+5——>1+2+3+4=(1+2+3)+4——>(1+2+3)=(1+2)+3——>1+2=(1)+2——>1=1
第二阶段:回归。从已知的条件出发,按照递推的逆过程,逐一求值回归,最后达到递推的开始处,结束回归阶段,完成递归调用。

递推不能是无限的,否则无法回归,所以递归调用必须有结束条件,不然会陷入无限的状态,永远无法结束调用。
实例1:先讲一下课本上的常用例子吧。求n!
在前面,针对该问题,我们采用了循环语句来实现的。其实,也可以采用递归来实现该问题。

#include<stdio.h>
unsigned fac(unsigned n)
{
	unsigned f;
	if(n==0)
		f=1;
	else
		f=fac(n-1)*n;
	return f;
}
int main()
{
	unsigned n;
	scanf("%u",&n);
	printf("%u!=%u\n",fac(n));
	return 0;
}

注意:对同一个函数的多次不同调用中,编译器会为函数的形参和局部变量分配不同的空间,它们互不影响。例如,在执行fac(2)时调用fac(1),编译器会用1为被调函数fac的形参n初始化,但不会改变主调函数fac中的形参n的值2。当fac(1)返回后,主调函数fac中的形参值仍然为2,同理,局部变量f也一样。
实例2:采用递归调用来计算1+2+3+…+n。具体程序如下:

#include<stdio.h>
int GetSum(int n)
{
	if(n==1)
		return 1;
	else
		return GetSum(n-1)+n;
}
int main()
{
	int m;
	scanf("%d",&m);
	printf("%d\n",GetSum(m));
	return 0;
}

实例3:设有一对新生兔子,从第3个月开始,它们每个月都生一对兔子,新生的兔子也如此繁殖。假设兔子没有死亡,问一年后,共有多少兔子?
在前面,针对该问题,我们采用的迭代的方法来解决的。经过前面的分析,我们知道:
第3个月的兔子对数=第2个月的兔子对数+第1个月的兔子对数(因为第1个月的兔子在第3个月时开始生宝宝了)。
同理,第4个月的兔子对数=第3个月的兔子对数+第2个月的兔子对数(因为第2个月的兔子在第4个月时开始生宝宝了)。
同理,第5个月的兔子对数=第4个月的兔子对数+第3个月的兔子对数(因为第3个月的兔子在第6个月时开始生宝宝了)。
。。。
依次类推,可知这是一个递归问题,month月的兔子总数等于month-1月的兔子数+month-2月的兔子数。

#include<stdio.h>
int RabNum(int month)
{
	if(month==1||month==2)
		return 1;
	return RabNum(month-1)+RabNum(month-2);
}
int main()
{
	int month=12;
	printf("%d\n",RabNum(month));
	return 0;
}

实例4:递归调用的神奇应用——解决汉诺塔问题!
有一个梵塔,塔内有3个座A、B和C,最初时A座上有64个盘子,盘子大小不等,大的在下,小的在上。要求把这64个盘子从A座移到C座,在移到过程中可以借助B座,每次只允许移动一个盘子,且在移到过程中始终保持大盘在下,小盘在上。请编程实现移动的过程。
分析:假如A座上有3个盘子,可以通过:
A——>C,A——>B,C——>B,A——>C,B——>A,B——>C,A——>C这7个步骤来实现移动。
假如是更多盘子呢,我们该怎么移动,假如是n个盘子呢?这个步骤将异常复杂。但采用递归的思想可以轻易实现。可通过3步来完成:
步骤1:将A座上的n-1个盘子借助C座移动到B座上;
步骤2:将A座上剩下的1个盘子移到C座上;
步骤3:将B座上的n-1个盘子借助A座移到C座上。

#include<stdio.h>
void move(char src,char dest)
{
	printf("%c-->%c\t",src,dest);
}
void hanoi(int n,char a,char b,char c)
{
	if(n==1)
		move(a,c);
	else
		{
			hanoi(n-1,a,c,b);
			move(a,c);
			hanoi(n-1,b,a,c);
		}
}
int main()
{
	int n;
	scanf("%d",&n);
	hanoi(n,'A','B','C');
	return 0;
}

问题:计算一下把n个盘子从A座移到C座所需的移动次数是多少?
分析:当n=1时,只需把盘子从A座移到C座,则f(1)=1;
当n=2时,将A座上面的那个盘子移到B座,然后将A座下面的那个盘子移到C座,最后将B座上的盘子再移到C座,则f(2)=3;
当n=3时,A——>C,A——>B,C——>B,A——>C,B——>A,B——>C,A——>C这7个步骤来实现移动,即f(3)=7

依次类推,可以得到如下规律:f(n)=2*f(n-1)+1

#include<stdio.h>
int getNum(int n)
{
	if(n==1)
		return 1;
	else
		return 2*getNum(n-1)+1;
}
int main()
{
	int n,num;
	scanf("%d",&n);
	num=getNum(n);
	printf("%dpierce needs %d\n",n,num);
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值