C语言经典教程8讲(3-25).ppt
1 概述概述2 函数定义的一般形式函数定义的一般形式 参数参数和和返回值返回值3 函数的调用函数的调用 函数原型函数原型 嵌套调用、递归调用嵌套调用、递归调用第八章第八章 函数函数14 数组数组作为函数的参数作为函数的参数5 局部变量和全局变量局部变量和全局变量6 变量的存储类别变量的存储类别 静态存储方式静态存储方式与与动态存储方式动态存储方式7 内部函数与外部函数内部函数与外部函数2C C程序结构程序结构1 概概 述述3 C函数的分类(函数的分类(从用户使用的角度分类)从用户使用的角度分类)库函数库函数由系统提供,由系统提供,printf(),sqrt().用户自定义函数用户自定义函数用户自己编写用户自己编写#include int add(int x,int y)return(x+y);main()int a,b,sum;sum=add(a,b);printf(“sum=%d”,sum);add()是自定义函数是自定义函数Printf()是库函数是库函数41、函数的概念、函数的概念从外部来看,函数就是一个数据加工厂。从外部来看,函数就是一个数据加工厂。/*自定义函数自定义函数*/int max(int x,int y)int z;z=xy?x:y;return z;c=max(a,b);传送数据返回结果2、采用自定义函数的意义、采用自定义函数的意义 符合结构化设计思想,符合结构化设计思想,每个自定义函数完成一个功能,每个自定义函数完成一个功能,可单独编译,可单独编译,便于设计、调试。便于设计、调试。一个函数可被多次调用,避免在程序中设计重复的代码一个函数可被多次调用,避免在程序中设计重复的代码。51.有参函数的定义有参函数的定义 类型标识符 函数名(形式参数表)函数头 局部声明 函数体 执行语句 2.无参函数的定义无参函数的定义 类型标识符 函数名()局部声明 执行语句 2 函数定义的一般形式函数定义的一般形式6例如:例如:/*定义无参函数定义无参函数*/*定义有参函数定义有参函数*/void star()int max(int x,int y)int z;printf(“*n”);z=xy?x:y;return(z);函数定义时,注意:函数定义时,注意:函数名的括号后无函数名的括号后无“;”必须指明每个必须指明每个参数的类型参数的类型,和,和函数值的类型函数值的类型.函数值类型函数值类型:函数返回值的类型。:函数返回值的类型。如:如:int float char void 等等,缺省按,缺省按整型整型处理。处理。无返回值的函数,函数值类型用无返回值的函数,函数值类型用void.73.函数的参数和函数返回值函数的参数和函数返回值形式参数形式参数 简称实参,主调函数中提供的数据。简称实参,主调函数中提供的数据。实际参数实际参数 简称形参,被调函数中用以接收主调函数数据的简称形参,被调函数中用以接收主调函数数据的 变量。变量。函数返回值函数返回值c=max(a,b);(main 函数)函数)实参实参 a,b(max 函数)函数)形参形参x,y 返回值返回值 zmax(int x,int y)int z;z=xy?x:y;return(z);8main()int a,b,c;scanf(%d%d”,&a,&b);c=max(a,b);实参实参 printf(“Max is%d n”,c);int max(int x,int y)形参形参 int z;z=xy?x:y;return(z);输入:输入:-100 200-100 200输出:输出:Max is 200Max is 200 例:例:调用函数时的数据传递调用函数时的数据传递9n关于参数的说明:关于参数的说明:1、形参和实参的对应:形参和实参的个数要一样多,位置一、形参和实参的对应:形参和实参的个数要一样多,位置一一对应,类型要匹配一对应,类型要匹配 如:如:d=max(7.8,10.2);/*实、形参类型不一致实、形参类型不一致*/m=max(a,b,c);/*实、形参个数不一致实、形参个数不一致*/2、实参可以是常量、变量、表达式,总之、实参可以是常量、变量、表达式,总之要有确定的值。要有确定的值。如如 max(2,b+c)3、实参对形参的数据传递是、实参对形参的数据传递是“值传递值传递”,即单向传递。,即单向传递。当当函数调用时,将函数调用时,将实参的值传递给形参实参的值传递给形参,而不能由形参传递,而不能由形参传递给实参。给实参。若是数组名,则传送的是数组的首地址。若是数组名,则传送的是数组的首地址。10参数传递举例:参数传递举例:利用自定义函数,找出三个数中的最大数。利用自定义函数,找出三个数中的最大数。main()int a,b,c,d;scanf(%d,%d,%d,&a,&b,&c);d=max(a,b);/*a、b的值作为实参的值作为实参*/d=max(d,c);/*d、c的值作为实参的值作为实参*/printf(Max is%dn,d);max(int x,int y)int z;z=xy?x:y;return(z);/*z值作为返回值值作为返回值*/118585812812假设输入a,b,c 的值:8,5,121)函数调用前,形参不占内存单元;函数调用后,形参占用的存储单元将被释放;例中第一次调用:实参:a b d=max(a,b);int max(int x,int y)形参:x y第二次调用:实参:d c d=max(d,c);形参:x y 122)实参和形参占据不同的存储单元,因此在被调函数中给形参变量赋值,不会对实参造成任何影响。13例:例:形、实参占据的是不同的存储单元形、实参占据的是不同的存储单元 main()int a=2,b=3;printf(“a=%d,b=%d n”,a,b);printf(“&a=%x,&b=%xn”,&a,&b);add(a,b);printf(“a=%d,b=%dn”,a,b);printf(“&a=%x,&b=%xn”,&a,&b);add(int x,int y)x=x+8;y=y+12;printf(“x=%d,y=%d n”,x,y);printf(“&x=%x,&y=%xn”,&x,&y);14main()int a,b,t;scanf(“%d%d”,&a,&b);t=a;a=b;b=t;printf(“%d,%d”,a,b);输入:3 5输出:5,3例:交换两个数,例:交换两个数,a和和b35abt353main()void swap(int,int);int a,b;scanf(“%d%d”,&a,&b);swap(a,b);printf(“%d,%d”,a,b);void swap(int x,int y)int t;t=a;a=b;b=t;输入:3 5输出:3,5x,y交换了,但交换了,但a,b的值不变的值不变154、由于实参和形参各有各的存储单元,因而实参和 形参可以同名,互不干扰;5、调用无参函数,无数据传送。16函数的返回值函数的返回值若返回一个值:可用return返回语句实现;若需返回多个值:则需要使用其它手段实现;如:指针、全局变量返回语句的一般形式:返回语句的一般形式:return(表达式);c=max(a,b);(main 函数)(max 函数)max(int x,int y)int z;z=xy?x:y;return(z);17使用说明:使用说明:一个自定义函数中可以有一个以上的一个自定义函数中可以有一个以上的return语句;语句;这常用于分支结构的不同出口,但只可能有一个被执行。这常用于分支结构的不同出口,但只可能有一个被执行。如上例:如上例:if(xy)return(x);else return(y);若函数不需要用若函数不需要用return语句返回值,其类型应采用语句返回值,其类型应采用void(空类型)标识符;(空类型)标识符;void printstar()printf(*n);该函数不需要参数,也没有返回值。该函数不需要参数,也没有返回值。18return后表达式值的类型一般应与定义函数的类型一后表达式值的类型一般应与定义函数的类型一致;若不一致,则以定义函数时的类型为准自动转换。致;若不一致,则以定义函数时的类型为准自动转换。如:如:int add(int x,int y)int sum;return(sum);/*若若 float sum;值为实型,自动转为值为实型,自动转为int型返回型返回*/191.函数调用的一般形式函数调用的一般形式 函数名(实参表)函数名()注意:注意:多个实参逗号分隔;调用无参函数,()不能省 如:如:c=max(a,b);printf(“%d”,sum);print();3 函数的调用函数的调用202、函数调用的三种方法、函数调用的三种方法 1).函数语句:函数语句:如:如:print();2).函数表达式:函数调用出现在表达式中,要求函数表达式:函数调用出现在表达式中,要求函数返回一个确定值,以参加表达式的运算。函数返回一个确定值,以参加表达式的运算。如:如:c=2*max(a,b);3).函数参数:函数调用作为另一个函数的参数,函数参数:函数调用作为另一个函数的参数,如:如:m=max(a,max(b,c);printf(“%d”,max(a,b);213 3、函数调用成功的前提条件、函数调用成功的前提条件1.1.被调函数必须被调函数必须存在存在(标准或用户定义)。(标准或用户定义)。2.2.若若调调用用的的是是库库函函数数,应应在在文文件件开开头头用用#include#include命命令令包包含含对对应的头文件。应的头文件。如:如:#include “stdio.h”#include “math.h”3 3.若若调调用用的的是是自自定定义义函函数数,一一般般应应在在主主调调函函数数中中对对被被调调函函数数的类型作声明。的类型作声明。(函数原型)(函数原型)形式:形式:函数类型函数类型 函数名函数名(参数类型参数类型1,1,参数类型参数类型2 2););或者或者函数类型函数类型 函数名函数名(参数类型参数类型1,参数名参数名1,参数类型参数类型2,参数名参数名2);22例例:在主调函数中,对被调用的自定义函数做说明在主调函数中,对被调用的自定义函数做说明 main()float add(float x,float y);/*对被调函数的声明对被调函数的声明*/float a,b,c;scanf(%f%f”,&a,&b);c=add(a,b);/*调用调用add函数函数*/printf(“sum is%f n”,c);float add(float x,float y)/*自定义函数自定义函数*/float z;z=x+y;return(z);输入:输入:3.6 6.5 输出:输出:sum is 10.10000float add(float,float)注意:对函数的注意:对函数的“定义定义”和和“声明声明”不是一回事不是一回事23问题:为什么要对被调函数做声明?问题:为什么要对被调函数做声明?提前向编译系统声明将要调用此函数,并将被调函数的提前向编译系统声明将要调用此函数,并将被调函数的有关信息(函数名、函数类型以及形参的类型、个数和顺有关信息(函数名、函数类型以及形参的类型、个数和顺序)通知编译系统,以便系统进行对照检查序)通知编译系统,以便系统进行对照检查。防止运行错误。防止运行错误。特别地,以下两种情况可以省略在主调函数中对被调函数特别地,以下两种情况可以省略在主调函数中对被调函数的声明。的声明。1)被调函数定义在主调函数之前)被调函数定义在主调函数之前2)在)在main函数外部,文件的开头处已经对被调函数作过说函数外部,文件的开头处已经对被调函数作过说明明24例例1 1:被调函数出现在主调函数之前,则在主调函:被调函数出现在主调函数之前,则在主调函数中不必声明。数中不必声明。输入:输入:3.6 6.5 输出:输出:sum is 10.100000float add(float x,float y)float z;z=x+y;return(z);main()float a,b,c;scanf(“%f%f”,&a,&b);c=add(a,b);printf(“%f n”,c);25例例2:在文件开头对被调函数声明:在文件开头对被调函数声明 char letter(char,char);float f(float,float);/*声明*/main()/*调用letter函数,f函数*/letter(c1,c2);f(x,y);char letter(char c1,char c2)/*定义letter函数*/float f(float x,float y)/*定义f函数*/264、函数的嵌套调用函数的嵌套调用 注意:函数调用结束后是一级一级的返回调用处注意:函数调用结束后是一级一级的返回调用处27问题:问题:某个函数能不能调用自己?某个函数能不能调用自己?5、函数的递归调用、函数的递归调用28递归:递归:在函数调用过程中,在函数调用过程中,直接或间接的调用自身。直接或间接的调用自身。1.1.直接递归:在函数体内又调用自身直接递归:在函数体内又调用自身 2.2.间接递归间接递归:当函数去调用另一函数时当函数去调用另一函数时,而另一函数反过来而另一函数反过来又调用自身又调用自身 3、要有递归结束条件、要有递归结束条件29例:有例:有5 5个人在一起问年龄,第个人在一起问年龄,第5 5个人比第个人比第4 4个人大个人大2 2岁,岁,第第4 4个人比第个人比第3 3个人大个人大2 2岁岁.第第2 2个人比第个人比第1 1个人大个人大2 2岁,岁,第第1 1个人为个人为1010岁。岁。递归结束的条件:递归结束的条件:age(1)=1030求年龄程序求年龄程序age(int n)int c;if(n=1)c=10;else c=2+age(n-1);return(c);main()printf(“%d n”,age(5);运行结果:运行结果:18 31 递推与递归递推与递归递推递推:从一个已知的事实出发从一个已知的事实出发,按一定规律推出下一个事按一定规律推出下一个事实实,再从已知的新的事实再从已知的新的事实,推出下一个新的事实推出下一个新的事实.例例 用递推法求用递推法求n!n!从从 1*2*3*n1*2*3*n32递归递归:在函数调用自身时在函数调用自身时,要给出结束递归的条件要给出结束递归的条件 例例 用递归法求用递归法求n!n!float fac(int n)float f;if(n=0|n=1)f=1;else f=n*fac(n-1);return(f);main()int n;float y;scanf(%d”,&n);y=fac(n);printf(“%d!=%15.0f n”,n,y);33函数调用形式:函数名(实参表列)函数调用形式:函数名(实参表列)数组元素作实参数组元素作实参数组名作实参数组名作实参4 数组作为函数参数数组作为函数参数341 1、数组元素作函数实参、数组元素作函数实参 与变量作实参一样,是实参到形参的与变量作实参一样,是实参到形参的“值传递值传递”。形参是相应类型的变量。形参是相应类型的变量。例:比较两数组例:比较两数组 a5,b5a5,b5,各对应元素两两比较。,各对应元素两两比较。思路:思路:主函数中主函数中 自定义函数自定义函数 m m 记录记录 aiai=bibi 的次数的次数 比较两个数组中对应比较两个数组中对应 n n 记录记录 aiaibibi 的次数的次数 元元素的大小素的大小 ai与与bi k k 记录记录 aiaiknk,认为,认为 abab;若;若 nknk,认为,认为 abab;否则认为;否则认为a=ba=b35 main()int large(int x,int y);/*函数声明函数声明*/int a5,b5,i,n=0,m=0,k=0;for(i=0;i5;i+)scanf(%d,&ai);printf(n);for(i=0;i5;i+)scanf(%d,&bi);printf(n);for(i=0;ibi的次数的次数*/else if(large(ai,bi)=0)m=m+1;/*ai=bi的次数的次数*/else k=k+1;/*aibi%d time n,n);printf(ai=bi%d time n,m);printf(aik)printf(array a is large than array b n);else if(ny)flag=1;else if(x bi 3 time ai=bi 1 time ai bi 1 time array a is large then array b xy372 2、数组名作为函数实参数组名作为函数实参 数组名作函数实参,形参也要是数组名(或指针)数组名作函数实参,形参也要是数组名(或指针)传送的是数组的首地址。传送的是数组的首地址。例:例:一维数组一维数组score,score,内存内存5 5个学生的成绩,求出平均成绩。个学生的成绩,求出平均成绩。思路:思路:主函数主函数 main()main()自定义函数自定义函数 average()average()输入数组输入数组score5 score5 求平均成绩,并返回其值求平均成绩,并返回其值38float average(float array5)int i;float aver,sum=0;for(i=0;i5;i+)sum=sum+arrayi;aver=sum/5.0;return(aver);main()float score5,aver;int i;printf(“input 5 score:n”);for(i=0;i5;i+)scanf(“%f”,&scorei);printf(“n”);aver=average(score);printf(“average score is%5.2f n”,aver);运行:运行:input 5 score:66 77 88 99 100结果:结果:average score is 86.00 实参是数组名实参是数组名形参也是数组名形参也是数组名39起始地址起始地址实参数组实参数组score:s0 s1 s2 s3 s4 形参数组形参数组array:a0 a1 a2 a3 a4 因此,形参与实参数组占据相同的内存单元。因此,形参与实参数组占据相同的内存单元。说明:说明:1.数组名作参数时,数组名作参数时,传递的是数组的首地址传递的是数组的首地址,对形参数组,对形参数组2.的操作实际上也是对实参数组的操作。的操作实际上也是对实参数组的操作。2.形参、实参的数组类型要一致,大小一般相等形参、实参的数组类型要一致,大小一般相等,以保证,以保证数据的全部传送;数据的全部传送;3.形参数组大小可以不指定;形参数组大小可以不指定;float average(float array)40n例:用函数实现冒泡排序(例:用函数实现冒泡排序(对形参排序就是对实参排序对形参排序就是对实参排序)void sort(int a,int n)int i,j,t;for(i=1;i=n-1;i+)for(j=0;jaj+1)t=aj;aj=aj+1;aj+1=t;main()int i;int array8=6,2,17,8,10,26,15,60;sort(array,8);for(i=0;ib?a:b;形参a,b 作用范围return(c);main()int a=8;/*a为局部变量*/局部变量a的作用范围printf(%d,max(a,b);全局变量b的作用范围50n从变量的作用域角度分,局部变量和全局变量。n从变量的生存期角度分,动态存储方式和静态存储方式。51静态存储区动态存储区变量对象存储方式生存期静态局部变量、全局变量静态程序运行全过程(自动)局部变量、形参变量动态函数被调用期间变量的存储方式及生存期:变量的存储方式及生存期:位置固定临时分配静态局部变量全局变量(自动)局部变量形参变量用户数据区动态存储与静态存储动态存储与静态存储变量的存储类别变量的存储类别52531.变量的存储方式变量的存储方式共有四种:auto自动 static静态 register寄存器 extern外部2.局部变量的存储方式局部变量的存储方式 可采用:auto、static、registerregister:用CPU的通用寄存器保存变量;特点是访问速度快,适合于使用频率高的变量。54auto:函数内部没加static说明的变量称为自动变量,存在动态存储区。特点:调用函数时临时分配存储单元,调用结束后释放。未赋初值时,其值未定义,每次调用重新赋值。未赋初值时,其值未定义,每次调用重新赋值。通常,auto存储类别的说明都省略不写,如:auto int i;写成 int i;55static:在函数内部加了static说明的变量称为局部静态变量,存在静态存储区存储区。函数调用结束后存储单元不释放。若希望函数若希望函数调用结束后,其值不消失调用结束后,其值不消失,下次调,下次调用函数时继续使用,则用用函数时继续使用,则用static对变量加以声对变量加以声明。明。56例例1:考察静态局部变量的值。:考察静态局部变量的值。f(int a)int b0;static int c3;bb1;cc1;return(abc););main()()int a2,i;for(i0;i3;i)printf(d,f(a););运行结果为:运行结果为:78957 说明:局部静态变量是在编译时赋初值的,所以每次 调用时不再重新赋初值,用的是上次保留的值。局部静态变量,只能被本函数使用。局部静态变量不赋初值时,编译系统自动赋 值0。局部动态变量若未赋初值,其值是不确定的,所分配的存储单元是不固定局部动态变量若未赋初值,其值是不确定的,所分配的存储单元是不固定的;的;而局部静态变量未赋初值,其值为而局部静态变量未赋初值,其值为0(字符型变量的值为字符型变量的值为0),所分,所分配的存储单元是固定的。配的存储单元是固定的。使用局部静态变量有如下几种情况使用局部静态变量有如下几种情况(1)需要保留上一次调用结束时的值需要保留上一次调用结束时的值(2)初始化后变量只被引用而不改变其值,则用静态局部初始化后变量只被引用而不改变其值,则用静态局部变量较方便,缺点:从程序运行开始到结束一直占用内变量较方便,缺点:从程序运行开始到结束一直占用内存,这样会浪费系统资源。存,这样会浪费系统资源。58例例2:打印:打印1到到5的阶乘值。的阶乘值。int fac(int n)static int f1;ff*n;return(f);main()int i;for(i1;i5;i)printf(d!dt,i,fac(i);运行结果为:运行结果为:1!12!2 3!6 4!24 5!120问题:问题:1.将将static 改为改为auto 2.其它函数能否使用局部静态变量其它函数能否使用局部静态变量?59n全局变量的存储方式 全局变量存在静态存储区。1)在一个文件中声明外部变量main()extern A,B;printf(“%d”,A+B);int A=2,B=3;int A=2,B=3;main()printf(“%d”,A+B);602)在多文件的程序中声明外部变量定义时用extern说明的全局变量,允许其它文件使用,定义时extern可省略定义时用static说明的全局变量,只在本文件内有效。在引用其它文件中的全局变量时,引用处要用extern说明,不能省略。61int data;main()extern int data;static int data2;file1.cfile2.cfile3.c定义为全局变定义为全局变量,可被其它量,可被其它文件使用文件使用引用其他文件中定义引用其他文件中定义的全局变量的全局变量定义全局变定义全局变量,只限本量,只限本文件使用文件使用62关于作用域关于作用域 和生存期和生存期 的概念的概念631.内部函数内部函数用static标识的函数,只能被本源程 序文件中的函数调用;例:static float f1(int x,int y)/*限定f1不对外*/2.外部函数外部函数不作标识的函数,允许被其它源程 序文件中的函数调用;但必须在调 用的源文件中作外部函数声明;例:file1.c file2.c int fun1(int x)extern int fun1(int x);内部函数与外部函数内部函数与外部函数64TC中多个源文件的中多个源文件的C程序调试:程序调试:两种方法:两种方法:1、利用项目文件实现2、利用包含命令实现 65利用项目文件实现:利用项目文件实现:1)首先分别编辑、编译file1.c和file2.c;2)在TC下编辑一个项目文件,扩展名为.PRJ;如:文件内容c:1109zyccprgfile1.c c:1109zyccprgfile2.c 文件内容指出了要参加联调的源程序文件名;并存入c:1109zyccprgMYPRG.PRJ3)选择菜单:project|project name 会话输入:c:1109zyccprgMYPRG.PRJ4)编辑状态按F9,将按照MYPRG.PRJ文件的指示 编译、链接,生成一个MYPRG.EXE文件。66利用包含命令实现:利用包含命令实现:1)首先分别编辑、编译file1.c和file2.c;2)在file1.c中使用文件包含预编译命令:#include c:1109zyccprgfile2.c3)启动编译操作,在正式编译之前的预编译阶段,file2.c的内容将被包含到file1.c中;正式编译阶段,被编译的内容是file1.c、file2.c合并内容。4)启动链接操作,生成一个可执行文件file1.exe。67小结小结n函数的定义:参数和返回值函数的定义:参数和返回值n函数的调用:实参到形参的值传递函数的调用:实参到形参的值传递n数组名作函数的参数数组名作函数的参数n局部变量和全局变量(作用域)局部变量和全局变量(作用域)n动态存储与静态存储(生存期)动态存储与静态存储(生存期)n习题习题 8.3 8.4 8.6 8.9 68