《学期C程序设计(第三版)教材-第8章-2讲稿.ppt》由会员分享,可在线阅读,更多相关《学期C程序设计(第三版)教材-第8章-2讲稿.ppt(55页珍藏版)》请在三一办公上搜索。
1、,第八章,函数,8.1 概述 8.2 函数定义的一般形式 8.3 函数参数和函数的值 8.4 函数的调用 8.5 函数的嵌套调用 8.6 函数的递归调用 8.7 数组作为函数参数 8.8 局部变量和全局变量简 8.9 变量的存储类别*8.10 内部函数和外部函数,主要内容,语言不能嵌套定义函数,但可嵌套调用函数,即在调用一个函数的过程中,又调用另一个函数。,8.函数的嵌套调用,例 8.用弦截法求方程 f(x)=x3-5x2+16x-80=0的根,8.函数的嵌套调用,取两个不同点x1,x2,如果f(x1)和f(x2)符号相反,则(x1,x2)区间内必有一个根。如果f(x1)与f(x2)同符号,则
2、应改变x1,x2,直到f(x1)、f(x2)异号为止。注意x1、x2的值不应差太大,以保证(x1,x2)区间内只有一个根。(2)连接(x1,f(x1)和(x2,f(x2)两点,此线(即弦)交x轴于x,方法:,(3)若f(x)与f(x1)同符号,则根必在(x,x2)区间内,此时将x作为新的x1。如果f(x)与f(x2)同符号,则表示根在(x1,x)区间内,将x作为新的x2。,(4)重复步骤(2)和(3),直到 f(x)一个很小的数,例如 1e-6.此时认为 f(x)0,例 8.用弦截法求方程 f(x)=x3-5x2+16x-80=0的根,8.函数的嵌套调用,方法:,N-S流程图,例 8.用弦截法
3、求方程 f(x)=x3-5x2+16x-80=0的根,8.函数的嵌套调用,分别用几个函数来实现各部分功能:,(1)用函数f(x)代表x的函数:x3-5x2+16x-80.(2)调用函数xpoint(x1,x2)求(x1,f(x1)和(x2,f(x2)的连线与x轴的交点x的坐标。(3)调用函数root(x1,x2)求(x1,x2)区间的实根。执行root函数要用到函数xpoint,而执行xpoint函数过程中要用到f函数。,例 8.用弦截法求方程 f(x)=x3-5x2+16x-80=0的根,8.函数的嵌套调用,#include#include float f(float x)/*函数实现f(x
4、)=x3-5x2+16x-80=0*float y;=(x-5.0)*x+16.0)*x-80.0;return y;float xpoint(float x1,float x2)*xpoint函数求弦与x轴交点*/float y;y=(x1*f(x2)-x2*f(x1)/f(x2)-f(x1);return y;float root(float x1,float x2)/*root函数求近似根*/float x,y,y1;y1=f(x1);do x=xpoint(x1,x2);y=f(x);if(y*y1)y1=y;x1=x;else x2=x;while(fabs(y)=0.0001);r
5、eturn x;,例 8.用弦截法求方程 f(x)=x3-5x2+16x-80=0的根,void main()float x1,x2,f1,f2,x;do printf(input x1,x2:n);scanf(f,%f,x1,x2);f1f(x1);f2f(x2);while(f1*f2);root(x1,x2);printf(“root of equation is%8.4n”,);,运行情况如下:input x1,x2:2,6 root of equation is.0000,例 8.用弦截法求方程 f(x)=x3-5x2+16x-80=0的根,函数直接或间接的调用自身叫函数的递归调用。
6、,8.6 函数的递归调用,例如:int f(int x)int y,z;z=f(y);.return(2*z);,入栈,f1调用f2;f2调用f3;f3调用f4;f4调用f5.,8.6 函数的递归调用,说明:C编译系统对递归函数的自调用次数没有限制,每调用函数一次,在内存堆栈区分配空间,用于存放函数变量、返回值等信息,所以递归次数过多,可能引起堆栈溢出。,递归算法:在可计算性理论中占有重要地位,它是算法设计的有力工具,对于拓展编程思路非常有用。我们先从一个最简单的例子导入:,用递归算法求n!定义:函数 fact(n)=n!fact(n-1)=(n-1)!则有fact(n)=n*fact(n-1
7、)已知fact(1)=1,8.6 函数的递归调用,用递归算法求n!定义:函数 fact(n)=n!fact(n-1)=(n-1)!则有fact(n)=n*fact(n-1)已知fact(1)=1,从图可以想象:欲求fact(3),先要求fact(2);要求fact(2)先求fact(1)。就象剥一颗圆白菜,从外向里,一层层剥下来,到了菜心,遇到1的阶乘,其值为1,到达了递归的边界。然后再用fact(n)=n*fact(n-1)这个普遍公式,从里向外倒推回去得到fact(n)的值。,8.6 函数的递归调用,调用和返回的递归示意图,#include int fac(int n)float f;if
8、(n0)printf(“n0,dataerror!”);else if(n=0|n=1)f=1;else f=fac(n-1)*n;return(f);void main()int n;float y;printf(Input an integer number:);scanf(%d,例8.8:递归法求n的阶乘,y=fac(5);,n=5 f=fac(4)*5=?中断,f=fac(4)*5;,n=4 f=fac(3)*4=?中断,f=fac(3)*4;,n=3 f=fac(2)*3=?中断,f=fac(2)*3;,n=2 f=fac(1)*2=?中断,f=fac(1)*2;,n=1 f=1 返
9、回fac(1)的值,为了进一步理解递归的概念,将递归与递推做一比较。仍以求阶乘为例:递推是从已知的初始条件出发,逐次去求所需要的阶乘值。如求3!初始条件 fact(1)=1fact(2)=2*fact(1)=2fact(3)=3*fact(2)=6,递推相当于从菜心“推到”外层。而递归算法的出发点不放在初始条件,而放在求解的目标上,从所求的未知项出发逐次调用本身的求解过程,直到递归的边界。许多实际问题不可能或不容易找到显而易见的递推关系,这时递归算法就表现出了明显的优越性。,例8.7 有个人坐在一起,问第个人多少岁?他说比第个人大岁。问第个人岁数,他说比第个人大岁。问第个人,又说比第个人大岁。
10、问第个人,说比第个人大岁。最后问第个人,他说是10岁。请问第个人多大。,age()age()age()age()age()age()age()age()age()10可以用数学公式表述如下:age()10()age(n-1)(),8.6 函数的递归调用,#include int age(int)*求年龄的递归函数*int;*用作存放函数的返回值的变量*if(=)10;else age(n-1);return(c);void main()printf(,age();,运行结果如下:18,8.6 函数的递归调用,例8.9 Hanoi(汉诺)塔问题是一个古典数学问题,是一个用递归方法解题的典型例子。
11、问题:古代有一个梵塔,塔内有3个座A、B、C,开始时座上有64个盘子,盘子大小不等,大的在下,小的在上。一个老和尚想把这64个盘子从座移到座,每次只允许移动一个盘,且在移动过程中在3个座上都始终保持大盘在下,小盘在上。在移动过程中可以利用座,要求编程序打印出移动的步骤。,8.6 函数的递归调用,按递归思想可知:将个盘子从座移到座可以分解为以下3个步骤:(1)将上n-1个盘借助座先移到座上。(2)把座上剩下的一个盘移到座上。(3)将n-1个盘从座借助于座移到座上。,8.6 函数的递归调用,8.7.1 数组元素作函数实参,数组元素可以作为函数的实参,与用变量作实参一样,是单向传递,即“值传送”方式
12、。,8.数组作为函数参数,例8.10 有两个数组和,各有10个元素,将它们对应地逐个相比。如果数组中的元素大于数组中的相应元素的数目多于b数组中元素大于a数组中相应元素的数目,则认为a数组大于b数组,并分别统计出两个数组相应元素大于、等于、小于的次数。,n=0m=0k=0,例8.10 两个数组大小比较,变量n,m,k分别记录aibi,ai=bi,aibi的个数,8.数组作为函数参数,#include void main()int a10,b10,i,n=0,m=0,k=0;printf(Enter array a:n);for(i=0;ik)printf(“abn”);else if(nk)p
13、rintf(“abn”);else printf(“a=bn”);,int large(int x,int y)int flag;if(xy)flag=1;else if(xy)flag=-1;else flag=0;return(flag);,例8.10 两个数组大小比较,a,b,x,y,8.数组作为函数参数,数组名可作函数参数,此时形参应当用数组名或用指针变量。,例8.11 有一个一维数组score,内放10个学生成绩,求平均成绩。,8.7.2 数组名作函数参数,8.数组作为函数参数,#include void main()float average(float array10);/*函数
14、声明*/float score10,aver;int;printf(input 10 scores:);for(i=0;i10;i+)scanf(,scorei);printf(n);averaverage(score);printf(average score is 5.2n,aver);,float average(float array10)int;float aver,sumarray0;for(i=1;i10;i+)sumsumarrayi;aversum/10;return(aver);,运行情况如下:input 10 scores:100 56 78 98.5 76 87 99
15、67.5 75 97average score is 83.40,例 8.12 形参数组不定义长度,#include void main()float average(float array,int)float score_15 98.5,97,91.5,60,55;float score_210=67.5,89.5,99,69.5,77,89.5,76.5,54,60,99.5;printf(“the average of class A is%6.2fn”,average(score_1,5);printf(“the average of class B is%6.2fn”,average
16、(score_2,10);float average(float array,int)int;float aver,sum=array0;for(;)sumsumarrayi;aversum;return(aver);,运行结果如下:the average of class A is 80.40The average of class is 78.20,8.数组作为函数参数,注意:实参数组和形参数组类型要一致,C编译系统对形参数组大小不作检查。,array,score_1,a0 a1 a2 a3 a4 3 6 1 9 4 未排序时的情况 1 6 3 9 4 将5个数中最小的数1与a0对换 1
17、3 6 9 4 将余下的4个数中最小的数3与a1对换 1 3 4 9 6 将余下的3个数中最小的数4与a2对换 1 3 4 6 9 将余下的2个数中最小的数6与a3对换,至此完成排序,例 8.13 用选择法对数组中10个整数按由小到大排序。,8.数组作为函数参数,#include void main()void sort(int array,int);int a10,i;printf(enter the array);for(i=0;i10;i+)scanf(,ai);sort(a,10);printf(the sorted array);for(i=0;i10;i+)printf(,ai);
18、printf(n);void sort(int array,int)int i,j,k,t;for(i=0;in-1;i+)=;for(j=i+1;jn;j+)if(array array)=;=arrayk;arrayk=arrayi;arrayi=t;,例 8.13 用选择法对数组中10个整数按由小到大排序。,8.7.3.多维数组名作函数参数,例8.4#include void main()max_value(int array4);int a34=1,3,5,7,2,4,6,8,15,17,34,12;printf(max value is,max_value(a));max_value
19、(int array 4)/*形参数组第2维长度不可省*/int i,j,k,max;max=array00;for(i=0;imax)max=arrayij;return(max);,运行结果如下:Max value is 34,8.数组作为函数参数,在一个函数内部定义的变量是内部变量,只在本函数范围内有效,称为“局部变量”。,8.8.1局部变量,8.8局部变量和全局变量,float f1(int a)/*函数f1*/int b,c;a、b、c有效 char f2(int x,int y)/*函数f2*/int i,j;x、y、i、j有效 void main()/*主函数*/int m,n;
20、m、n有效,说明:(1)主函数中定义的变量只在主函数中有效,主函数也不能使用其他函数中定义的变量。(2)不同函数中可以使用相同名字的变量,它们在内存中占不同的单元,代表不同的对象,互不干扰。(3)形参也是局部变量,只在本函数中有效。(4)在一个函数内部,可以在复合语句中定义变量,这些变量只在本复合语句中有效,这种复合语句也称为“分程序”或“程序块”。,8.8.1局部变量,8.8局部变量和全局变量,void main()int a,b;int c;c=a+b;/*a,b,c在此范围内有效*/,在函数之外定义的变量称为外部变量,外部变量是全局变量(也称全程变量)。全局变量可以为本文件中其他函数所共
21、用。它的有效范围为从定义变量的位置开始到本源文件结束。,8.8.2 全局变量,8.8局部变量和全局变量,int p=1,q=5;/*外部变量*/float f1(int a)/*定义函数f1*/int b,c;char c1,c2;/*外部变量*/char f2(int x,int y)/*定义函数f2*/int i,j;void main()/*主函数*/int m,n;,全局变量p,q的作用范围,全局变量c1,c2的作用范围,例 8.15 有一个一维数组,内放10个学生成绩,写一个函数,求出平均分、最高分和最低分。,#include float Max,Min;*习惯定义全局变量首字母大写
22、*void main()float average(float array,int n);float ave,score10;int;for(i=0;i10;i+)scanf(%f,scorei);ave=average(score,10);printf(“max=%6.2fnmin=%6.2fn average=%6.2fn“,Max,Min,ave);,8.8.2 全局变量,8.8局部变量和全局变量,float average(float array,int n)*定义函数,形参为数组*/int;float aver,sum=array0;Max=Min=array0;for(=;)if(
23、arrayiMax)Maxarrayi;else if(arrayiMin)Min arrayi;sum=sum+arrayi;aversum;return(aver);,运行情况如下:99 45 78 97 100 67.5 89 92 66 43max100.00min43.00average77.65,说明:1、全局变量增加了函数间数据的联系渠道。2、建议不在必要时不要使用全局变量,原因如下:,全局变量在程序的执行过程中都占用存储单元。使用全局变量过多,会降低程序的清晰性,人们难以清楚地判断出每个瞬时各外部变量的值。函数的通用性降低。如将一个函数移到另一个文件中,还要将有关的外部变量及其
24、值一起移过去。但若该外部变量与其他文件的变量同名时,就会出问题。3、同一源文件中外部变量与局部变量同名则在局部变量的作用域内外部变量不起作用。,8.8局部变量和全局变量,例 8.16 外部变量与局部变量同名,#include int a=3,b=5;/*a,b为外部变量*/a,b作用范围void main()int a=8;/*a为局部变量*/局部变量a作用范围 printf(%d,max(a,b);全局变量b的作用范围 max(int a,int b)/*a,b为局部变量*/int c;c=ab?ab;形参a、b作用范围 return(c);,运行结果为8,8.8局部变量和全局变量,从变量的
25、作用域角度来分:全局变量和局部变量。那么从变量值存在的时间(即生存期)角度来分:静态存储方式和动态存储方式。,静态存储方式是程序运行期间由系统分配固定的存储空间的方式。动态存储方式是程序运行期间根据需要动态的分配存储空间的方式。存储空间可以分为三部分:程序区静态存储区:(如:全局变量)动态存储区:(函数形参、自动变量、函数调用的现场保护和返回地址),8.变量的存储类别,8.9.1 动态存储方式与静态存储方式,中每一个变量和函数有两个属性:数据类型和数据的存储类别。存储类别指的是数据在内存中存储的方式。存储方式分为:静态存储类和动态存储类。具体包含四种:自动的(auto),静态的(static)
26、,寄存器的(register),外部的(extern)。根据变量的存储类别,可以知道变量的作用域和生存期。,8.变量的存储类别,8.9.1 动态存储方式与静态存储方式,函数中的局部变量,如不专门声明为static存储类别,都是动态地分配存储空间的,数据存储在动态存储区中。函数中的形参和在函数中定义的变量(包括在复合语句中定义的变量),都属此类,在调用该函数时系统会给它们分配存储空间,在函数调用结束时就自动释放这些存储空间。因此这类局部变量称为自动变量。用关键字auto作存储类别的声明。例如:int(int)*定义f函数,为形参*auto int,;*定义、为自动变量*,8.变量的存储类别,au
27、to变量,如希望函数中的局部变量在函数调用结束后占用的存储单元不释放,下一次该函数被调用时,用到上一次函数调用结束时局部变量的值。应指定该局部变量为“静态局部变量”,用static声明。,8.变量的存储类别,8.9.3用static声明局部变量,例8.17 考察静态局部变量的值。#include void main()int f(int);int a=2,i;for(i=0;i3;i+)printf(%d,f(a);int f(int a)auto int b=0;static c=3;b=b+1;c=c+1;return(a+b+c);,对静态局部变量的说明:(1)静态局部变量属静态存储类别
28、,在静态存储区内分配存储单元。在程序整个运行期间都不释放。而动态局部变量(即自动变量)属动态存储类别,占动态存储区空间,函数调用结束后即释放。(2)对静态局部变量是在编译时只赋一次初值,程序运行时已有初值。以后每次调用函数时不再重新赋初值而只保留上次函数调用结束时的值。而对自动变量是在函数调用时赋初值,每调用一次函数重新给一次初值。,8.变量的存储类别,(3)如定义变量时不赋初值,则对静态局部变量,编译时自动赋初值(对数值型变量)或空字符(对字符变量)。而对自动变量因每次函数调用结束后释放存储单元,下次调用时另分配存储单元,单元中的值不确定。(4)虽然静态局部变量在函数调用后仍存在,但其他函数
29、不能引用它。,例8.18 输出到的阶乘值。#include void main()int fac(int);int;for(i=1;i=5;i+)printf(%!=,fac(i);int fac(int)static int f=1;/*目的是保留函数上次调用结束时的值*/*;return(f);其他需要用静态局部变量的情况:初始化后变量只被引用值而不做值得修改。,8.变量的存储类别,*8.9.4 register变量,一般,变量(包括静态和动态)的值是存放在内存中的。当程序中用到哪一个变量的值时,由控制器发出指令将内存中该变量的值送到运算器中。经过运算器进行运算,如果需要存数,再从运算器将
30、数据送到内存存放。,8.变量的存储类别,如一些变量使用频繁(例如:在一个函数中执行10000次循环,每次循环中都要引用某局部变量),则为节约变量存取时间,允许将局部变量的值放在CPU中的寄存器中,需要用时直接从寄存器取出参加运算,不必再到内存中去存取。由于对寄存器的存取速度远高于对内存的存取速度,因此这样做可以提高执行效率。这种变量叫做寄存器变量,用关键字register作声明。,略!,例8.19 使用寄存器变量-输出到n的阶乘的值#include void main()long fac(long);long i,n;scanf(%ld,8.变量的存储类别,略!,外部变量:是在函数的外部定义的
31、全局变量,作用域从变量的定义处开始,到本程序文件的末尾。在作用域内,全局变量可为程序中各个函数所引用。编译时将变量分配在静态存储区。有时需要用extern来声明外部变量,以扩展外部变量的作用城。,8.变量的存储类别,8.9.5 用extern声明外部变量,例8.20 用extern声明外部变量,扩展它在程序文件中的作用域。#include void main()int max(int,int);*外部变量声明*extern A,B;printf(%dn,max(A,B);int A=13,B=-8;*定义外部变量*int max(int x,int y)*定义函数*int z;z=xy?x:y
32、;return(z);,1.在一个文件内声明外部变量,2.在多文件的程序中声明外部变量,例8.21 用extern将外部变量的作用域扩展到其他文件。本程序的作用是给定的值,输入A和,求A和Am的值。文件file1.c中的内容为:#include int A;/*定义外部变量*/void main()int power(int);/*函数声明*/int 3,c,d,m;printf(enter the number a and its power m:n);scanf(,A,);A*;printf(*n,A,b,c);power(m);printf(*nA,m,d);,8.变量的存储类别,文件f
33、ile2.c中的内容为:extern A;/*声明A为一个已定义的外部变量*/int power(int n)int,;for(i=1;i=n;i+)*A;return(y);,8.9.6用static声明外部变量,有时希望限定某些外部变量只限于被本文件引用。可在定义外部变量时加一个static声明。例如:file1.c file2.cstatic int A;extern int A;/*无效扩展*/void main()void fun(int n)A=A*n;,8.变量的存储类别,8.9.7关于变量的声明和定义,对变量而言,声明与定义的关系稍微复杂一些。在声明部分出现的变量有两种情况:一
34、种是需要建立存储空间的“定义性声明”或简称定义(如:int a;),另一种是不需要建立存储空间的“引用性声明”(如:extern a;)。广义地说,声明包括定义,但并非所有的声明都是定义。对“int a;”而言,它既是声明,又是定义。而对“extern a;”它是声明而不是定义。,8.变量的存储类别,一般为叙述方便,把建立存储空间的声明称定义,而把不需要建立存储空间的声明称为声明。例如:void main()extern A;/*是声明不是定义。声明A是一个已定义的外部变量*/int A;,(1)从作用域分,有局部变量和全局变量。它们采用的存储类别如下:局部变量|动态局部变量,即自动变量(离开
35、函数,值就消失)|静态局部变量(离开函数,值仍保留)|寄存器变量(离开函数,值就消失)|(形式参数可以定义为自动变量或寄存器变量)全局变量|静态外部变量(只限本文件引用)|外部变量(即非静态的外部变量,允许其他文件扩展引用),8.变量的存储类别,8.9.8 存储类别小结,8.变量的存储类别,(2)从变量存在的时间(生存期)分:动态存储和静态存储。静态存储是程序整个运行时间都存在,而动态存储则是在调用函数时临时分配单元。动态存储|自动变量(本函数内有效)|寄存器变量(本函数内有效)|形式参数(本函数内有效)静态存储|静态局部变量(函数内有效)|静态外部变量(本文件内有效)|外部变量(其他文件可引
36、用),8.9.8 存储类别小结,(3)从变量值存放的位置分,可分为:内存中静态存储区|静态局部变量|静态外部变量(函数外部静态变量)|外部变量(可为其他文件引用)内存中动态存储区:自动变量和形式参数CPU中的寄存器:寄存器变量,(4)关于作用域和生存期的概念。前者是从空间的角度,后者是从时间的角度。二者有联系但不是同一回事。(5)static对局部变量和全局变量的作用不同。对局部变量,它使变量由动态存储方式改变为静态存储方式。对全局变量,它使变量局部于本文件,但仍为静态存储方式。从作用域角度看,凡static声明的,其作用域都是局限的,或者是局限于本函数内(静态局部变量),或局限于本文件内(静
37、态外部变量)。,8.9.8 存储类别小结,8.变量的存储类别,函数本质上是全局的,但也可指定函数不能被其他文件调用。根据函数能否被其他源文件调用,将函数区分为内部函数和外部函数。,如一个函数只能被本文件中其他函数所调用,它称为内部函数。定义内部函数一般形式:static 类型标识符 函数名(形参表)如:static int fun(int a,int b),8.10 内部函数和外部函数,8.10.1内部函数,8.10.2外部函数,(1)如一个函数可供其他文件调用,称为外部函数。定义外部函数一般形式:extern 类型标识符 函数名(形参表)如:extern int fun(int a,int
38、b)C规定,如在定义函数时省略extern,则隐含为外部函数。,(2)在需要调用此函数的文件中,用extern对函数作声明,表示该函数是在其他文件中定义的外部函数,略!,例 8.22 有一个字符串,内有若干个字符,今输入一个字符,要求程序将字符串中该字符删去。用外部函数实现。,file1.c(文件)#include void main()extern void enter_string(char str);extern void detele_string(char str,char ch);extern void print_string(char str);*以上3行声明在本函数中将要调用
39、的在其他文件中定义的3个函数*char c;char str80;enter_string(str);scanf(%c,8.10 内部函数和外部函数,略!,file2.c(文件)#include void enter_string(char str80)*定义外部函数enter-string*gets(str);*向字符数组输入字符串*file3.c(文件)#include void delete_string(char str,char ch)*定义外部函数delete_string*int i,j;for(i=j=0;stri!=0;i+)if(stri!=ch)strj+=stri;stri=0;,8.10 内部函数和外部函数,例 8.22 有一个字符串,内有若干个字符,今输入一个字符,要求程序将字符串中该字符删去。用外部函数实现。,略!,file4.c(文件)#include void print_string(char str)printf(%sn,str);,运行情况如下:(输入str)(输入要删去的字符)(输出已删去指定字符的字符串),8.10 内部函数和外部函数,例 8.22 有一个字符串,内有若干个字符,今输入一个字符,要求程序将字符串中该字符删去。用外部函数实现。,略!,8.1;8.38.138.14,作业:,