1、2018/11/15,C语言程序设计,1,第7章 函 数,主要内容 C程序的模块化结构 库函数 函数的定义 函数的调用 数组作为函数参数 由多个函数组成的C程序设计应用举例 核心知识点 C语言中常用库函数的使用 函数的定义及调用 数组作为函数参数 由多个函数组成的C程序设计,2018/11/15,C语言程序设计,2,第7章 函 数,重点难点 库函数、函数的定义及调用 函数的嵌套调用和递归调用 学习目标 了解C程序的模块化结构 了解并掌握C语言中常用库函数的使用方法 掌握函数的定义及调用方法 掌握数组作为函数参数时函数的定义及函数参数的设置方法 掌握用结构化程序设计的方法编制程序,2018/11
2、/15,C语言程序设计,3,7.1 C程序的模块化结构,7.1.1 概述 所谓模块化设计是将一个大的程序自上而下进行功能分解,分成若干个模块,模块对应了一个功能,有自已的界面,有相关的操作,完成独立的程序 。,2018/11/15,C语言程序设计,4,7-1 C程序的模块化结构,2018/11/15,C语言程序设计,5,C语言是一种现代化程序设计语言,它具有以下特点: C语言允许将一个程序写入不同的源文件,每一个源文件可以独立编译,一个源文件可以被不同的程序使用。 一个源文件由多个函数组成,函数是最小的功能单位,一个函数可以被不同的源文件的其他函数调用。 一个C程序有且仅有一个主函数main(
3、),主函数可以放在任何一个源文件中,程序的执行从主函数开始,主函数是系统定义的。 同一个程序的所有源文件可以通过工程文件组装成一个完整的程序。,2018/11/15,C语言程序设计,6,7.1.2 函数的种类 从用户使用的角度,可以将函数分为两种:库函数和用户自定义函数。库函数:也称为标准函数,在C语言的编译系统中,提供了若干已经编制好的函数,用户可以直接使用。不同的编译系统提供的库函数的名称是不完全相同的。用户定义函数:用户根据需要,遵循C语言的语法规定自己编写的一段程序,实现特定的功能。,2018/11/15,C语言程序设计,7,从函数的形式上看,可将函数分为有参函数和无参函数两种。无参函
4、数:使用该函数时,不需提供数据,直接根据该程序段提供的功能,通常是完成某一个处理任务。有参函数:使用该函数时,必须提供必要的数据,提供数据的不同,将可能获得不同的结果。,2018/11/15,C语言程序设计,8,【例7-1】一个函数的简单应用的实例。#include /*调用数学函数*/ float func(x,y) float x,y;return(pow(x,y); /*pow()为幂函数xy*/ main() flaot a,b,c; scanf(“%f,%f”, ,2018/11/15,C语言程序设计,9,7.2 库函数,7.2.1 C语言常用库函数对每一类库函数,在调用该类库函数时
5、,用户在源程序的include命令中包含该类库函数的头文件。 数学函数 调用数学库函数时,要求程序在调用数学函数前应包含下面的头文件:#include “math.h” 字符函数和字符串函数 调用字符函数时,要求程序在调用字符函数前应包含下面的头文件:#include “ctype.h” 调用字符串函数时,要求程序在调用字符串函数前应包含下面的头文件:#include “string.h” 输入、输出函数 调用输入、输出函数时,要求在源文件中应包含下面的头文件:#include “stdio.h” 动态分配函数和隨机函数 调用动态分配函数和隨机函数时,要求在源文件中应包含下面的头文件:#inc
6、lude “stdlib.h”,2018/11/15,C语言程序设计,10,7.2.2 标准库函数的调用 前面讲到,调用C语言标准库函数时必须在源程序中用include命令。 include命令的格式为: #include 或 #include “头文件名” 说明: include命令必须以#号开头,系统提供的头文件都以.h作为后缀,头文件用一对双引号(” ”)或一对尖括号()括起来。 在C语言语言中,调用库函数时不缺少库函数的头文件,include命令不是语句,不能在最后加分号。 两种格式的区别是:用尖括号时,系统到存放C库函数头文件所在的目录寻找要包含的文件,即标准方式;用双引号时,系统先
7、在用户目录中寻找要包含的文件,若找不到,再按标准方式查找。 标准库函数一般调用格式为:函数名(参数表),2018/11/15,C语言程序设计,11,【例7-2】 库函数的调用示例。#include /*调用函数strlen需要包含的头文件*/#include /*调用函数printf需要包含的头文件*/main() char str=”abcde”;int i;i=strlen(str); /*调用strlen函数*/printf(“%d”,i); /*调用printf函数*/程序运行结果为:5,2018/11/15,C语言程序设计,12,7.3 函数的定义,7.3.1 函数的定义 C语言中的
8、函数从函数定义的角度来讲分为两种基本类型:有参函数和无参函数。 1.无参函数的定义 类型标识符 函数名()声明部分语句,2018/11/15,C语言程序设计,13,【例7-3】 编写一个无参函数。 #include void fun1() printf(“* * * * * * * * * * * * * * * * *n“);printf(“ How are you!n“);printf(“* * * * * * * * * * * * * * * * *n“); main( ) fun1( ); ,2018/11/15,C语言程序设计,14,2.有参函数的定义 类型标识符 函数名(形式参数
9、表列)声明部分语句有参函数的定义比无参函数的定义多了一个内容,即形式参数表列。在形式参数表列中给出的参数称为形式参数,它们可以是各种类型的变量,各参数之间用逗号间隔。在进行函数调用时,主调函数将赋予这些形式参数实际的值。形参既然是变量,必须在形参表中给出形参的类型说明。 【例7-4】 有参函数示例int max(int a, int b) int z;z=xy?x:y;return(z);,2018/11/15,C语言程序设计,15,7.3.2 函数的返回值与函数类型,return语句的格式为return 表达式; 或 return (表达式); 如果没有返回值,格式中的左、右圆括号可以省略,
10、即写为:return;,2018/11/15,C语言程序设计,16,函数返回值的类型信赖于函数本身的类型,即函数类型决定返回值的类型。当返回值的数据类型与函数类型不一致时,对于数值型数据,可以自动进行类型转换。如果函数有返回值,则在函数定义和函数调用时,一般都应该指明返回值的类型。在函数定义时,返回值的类型在定义函数名的前面进行说明,如: float count(int n) float s;return(s); 其中函数名count前面的float,即用于说明return语句中的s返回值的数据类型。当函数有返回值时,在调用函数中,通常也应当对被调用函数的返回值类型在函数体中进行说明,如将返回
11、值说明为float型。如果被调用函数中没有return语句,即不要求被调函数有返回值时,为了明确表示“无返回值”,可用“void”定义无返回值函数,只需在定义函数时,在函数名前加上void即可。,2018/11/15,C语言程序设计,17,7.3.3 对被调用函数的说明和函数原型,函数调用的一般格式为: 函数名(实参表列); 1.对被调用函数的说明 一般在调用函数之前,应在主调函数中对被调函数进行说明。 C语言规定,在以下情况下,可以不在调用函数内对被调用函数进行类型说明: 当被调用函数的定义位于调用函数之前时,可以不必进行类型说明。如: float count(int n) float s;
12、 return(s); main() float s; s=count(10); 此时,由于count函数的定义位于main()函数调用之前,所以在main()函数中,可以不对其类型进行说明。,2018/11/15,C语言程序设计,18,(2)如果函数没有返回值或返回值的类型为整型或字符型,也可以不进行类型说明,系统将自动进行处理。但现在有的编译系统不允许这样,遇到这种情况,编译时将给出警告信息,因此,最好都进行说明。 (3)允许在所有函数的外面、文件的开头对函数的类型进行说明,这样在调用函数时就可以不对被调函数的类型进行说明,如: float count(); main() float s;
13、 s=count(10); float count(n) int n ; float s; return(s); 由于在文件的开始处已经对count函数进行了类型说明,因而在main函数中就不必对count函数再进行类型说明了,2018/11/15,C语言程序设计,19,2.函数原型在C语言中,以上的函数声明称为函数原型。使用函数原型是ANSI C的一个重要的特点。它的作用主要是利用它在程序的编译阶段对调用函数的合法性进行全面检查。函数原型的一般形式为: 函数类型 函数名(参数类型1,参数类型2) 函数类型 函数名(参数类型1,参数名1,参数类型2,参数名2)第一种形式是基本的形式。为了便于阅
14、读程序,也允许在函数原型中加上参数名,就成了第二种形式。但编译系统不检查参数名。如上面程序中的声明也可写成: float count(n);应当保证函数原型与函数首部写法上的一致,即函数类型、函数名、参数个数、参数类型和参数顺序必须相同。函数调用时函数名、实参个数与函数原型一致。实参类型必须与函数原型中的形参类型赋值兼容。,2018/11/15,C语言程序设计,20,7.3.4 函数的形参与实参,在调用函数时,大多数情况下,主调函数和被调函数之间有数据传递关系。这就是前面提到的有参函数。前面已提到:在定义函数时函数名后面括弧中的变量名称为“形式参数”(简称“形参”),在主调函数中调用一个函数时
15、,函数后面括弧中的参数(可以是一个表达式)称为“实际参数”(简称“实参”)。 【例7-5】 调用函数时的数据传递。 main() int a,b,c; scanf(“%d,%d”, ,max(int x,int y) int z; z=xy?x:y; return(z); 运行情况: 7,8 Max is 8,2018/11/15,C语言程序设计,21,关于形参和实参的说明: (1)在定义函数中指定的形参,在未出现函数调用时,它们并不占内存中的存储单元。只有在发生函数调用时,函数max中的形参才被分配内存单元。在调用结束后,形参所占的内存单元也被释放。 (2)实参可以是常量、变量或表达式,如:
16、 max(3,a+b); 但要求它们有确定的值。在调用时将实参的值赋给形参(如果形参是数组名,则传递的是数组的首地址而不是数组的值。) (3)在被定义的函数中,必须指定形参的类型。 (4)实参与形参的类型应相同或赋值兼容。上例中实参和形参都是整数,这是合法的、正确的。如果实参为整型而形参x为实型,或者相反,则按第3章介绍的不同类型数值规则进行转换。例如实参值a为3.5,而形参值x为整型,则将实数3.5转换为整数3,然后送给形参b。 (5)C语言规定,实参变量对形参变量的数据传递是“值传递”,即单向传递,只由实参传给形参,而不能由形参回传给实参。在内存中形参单元和形参单元是不同的单元,如图7-2
17、所示。图7-2 图7-3 在调用函数时,给形参分配存储单元,并将实参对应的值传递给形参,调用结束后,形参单元被释放,实参单元仍保留并维持原值。因此,在执行一个被调用函数时,形参的值如果发生改变,并不改变主调函数的实参的值。例如,若在执行函数过程中x和y的值变为10和15,而a和b仍为2和3,如图7-3所示。,2018/11/15,C语言程序设计,22,7.4 函数的调用,7.4.1 函数的简单调用 函数调用的一般形式为: 函数名(实参表列); 按照函数在主调函数中的作用,可以有以下三种函数调用方式: 1.函数语句 把函数调用作为一个语句,如: printf(“%d”,a); scanf(“%f
18、”, 此时不要求函数带回值,只要求函数完成一定的操作。,2018/11/15,C语言程序设计,23,2.函数表达式 函数出现在一个表达式中,这种表达式称为函数表达式。此时要求函数带回一个确实的值以参加表达式的运算。 如: c=pow(a,i)+pow(b,j); value=4+max(a,b);等, 函数pow(a,i),pow(b,j)和max(a,b)都只是表达式的一部分。 3.函数参数 函数调用的结果可以进一步做为其它函数的参数,此时函数调用也有返回值。 如: m=max(a,max(b,c); 其中max(b,c)是一次函数调用,其结果做为max函数又一次调用的实参。m的值为a,b,
19、c三者中的最大值。又如: printf(“%d”,max(a,b);也是将max(a,b)的值作为printf函数的一个参数。,2018/11/15,C语言程序设计,24,在函数调用中还应该注意的一个问题是求值顺序的问题。所谓求值顺序是指对实参表中变量是自左至右使用呢,还是自右至左使用。对此,各系统的规定不一定相同。在Turbo C中现定是自右至左求值。 【例7-6】 调用函数时数据的求值顺序。 main() int i=8;printf(“%d,%d,%d,%dn“,+i,-i,i+,i-); 如按照从右至左的顺序求值。运行结果应为:8,7,7,8,2018/11/15,C语言程序设计,25
20、,7.4.2 函数的嵌套调用 语言的函数定义都是互相平行的,独立的,一个函数定义中不能包含对另一个函数的定义,即函数不能嵌套定义。但是语言中允许在一个函数的定义中对另一个函数进行调用。这样就出现了函数的嵌套调用。如图7-4所示。,2018/11/15,C语言程序设计,26,图7-4 函数嵌套调用示意图,2018/11/15,C语言程序设计,27,【例7-7】 编程计算下列函数值。 f(x,y)=s(x)/s(y) 其中s(n)=p(1)+p(2)+p(n),p(i)=i!(i的阶乘)。 要求:p(i),s(n)和f(x,y)均编写成一个用户自定义函数。,程序如下: float p(int k)
21、 /* 用于求任意整数K的阶乘 */ int i;float f=1.0;for(i=1;i=k;i+)f=f*i;return(f); 续后,2018/11/15,C语言程序设计,28,续前 float s(int n) /* 用于求任意整数n的阶乘之和s(n) */ int j;float s=0.0;for(j=1;j=n;j+)s=s+p(j);return(s); float f(int x,int y) /* 用于函数f(x,y)的值 */ float k;k=s(x)/s(y);return(k); ,2018/11/15,C语言程序设计,29,续前 main() int x,y
22、;float result;scanf(“%d,%d“, 程序运行结果如下: 10,5 s(10)/s(5)=26391.59,2018/11/15,C语言程序设计,30,7.4.3 函数的递归调用 在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用。C语言的特点之一就在于允许函数的递归调用。如: int f(int x) int y,z;z=f(y);return(2*z); ,2018/11/15,C语言程序设计,31,递归调用分为直接递归和间接递归两大类,直接递归是指函数自己调用自己,即在函数f中又要调用f函数,这是直接调用函数本身。间接递归是通过另一个函数调用自
23、己,即函数f1调用函数f2,函数f2又调用函数f1。如下例所示。int f1( ) f2();int f2( ) f1();,2018/11/15,C语言程序设计,32,使用递归应注意两个问题: (1)通过递归应该一次次接近所求的解而不是离解越来越远; (2)应有递归结束的条件,不能无限止地递归下去。 【例7-8】用递归方法求n! 用递归法计算n!可用下述公式表示:n!=1 (n=0,1)n(n-1)! (n1),2018/11/15,C语言程序设计,33,按公式可编程如下: long ff(int n) long f;if(n0) printf(“n0,input error“);else
24、if(n=0|n=1) f=1;else f=ff(n-1)*n;return(f); main() int n;long y;printf(“ninput a inteager number:n“);scanf(“%d“, 运行结果如下: input a inteager number: 5 5!=120,2018/11/15,C语言程序设计,34,2018/11/15,C语言程序设计,35,7.5 数组作为函数参数,7.5.1 数组元素作为函数参数 【例7-9】数组元素做函数调用的实参。 #include main( ) int a10,b,j;for(j=0;jy?x:y); 数组元素作
25、为函数的实参,必须在主调函数内定义数组,并使其获得初值,通过函数调用将数组元素的值传递给对应的形参。此时数据传递与变量作函数的实参一样,是单向传递,即“值传递”方式。,2018/11/15,C语言程序设计,36,7.5.2 数组名作为函数的形参和实参 【例7-10】 一维数组score内存放5个学生的成绩,求平均成绩。 float average(float arr5) int j;float aver,sum=0;for (j=0;j5;j+)sum=sum+arrj;aver=sum/5;return(aver); main( ) float score5,aver; int j; pri
26、ntf(“input 5 scores:n“); for (j=0;j5;j+)scanf(“%f“, 运行结果如下: input 5 scores: 64 66 77.8 85 average score is:76.16,(1)数组名作为函数参数,应该在主调函数和被调函数中分别定义数组,如例7-10中arr是形参数组,score是实参数组,分别在其所在函数中定义。 (2)实参数组与形参数组类型应当相同,如果不同,将会出错。 (3)实参数组与形参数组大小可以相同也可以不同,C编译器对形参数组大小不做检查,只是将实参数组的首地址传递给形参数组。 (4)形参数组也可不指定大小,或者在被调函数中另
27、设一个参数,来传递数组的大小。,2018/11/15,C语言程序设计,37,【例7-11】数组名作函数参数 float average(float arr,int n) int j;float aver,sum=0;for (j=0;jn;j+)sum=sum+arrj;aver=sum/n;return(aver); main( ) float score15=80,66,70.5,82,90;float score26=68,72,70.5,88,68,90;printf(“the average of the first class is %6.2fn“, average(score1,
28、5);printf(“the average of the second class is %6.2fn“, average(score2,6); 运行结果如下: the average of the first class is 77.70 the average of the second class is 76.08,2018/11/15,C语言程序设计,38,【例7-12】数组名作函数实参 #include fun(char str) strcpy(str,“ASP.NET“); main( ) char a10=“Turbo C“;printf(“%sn“,a);fun(a);pri
29、ntf(“%s“,a); 运行结果为: Turbo C ASP.NET,2018/11/15,C语言程序设计,39,【例7-13】求一个34的矩阵中所有元素中的最大值。 max_value(int array4) int i,j,k, max;max=array00;for(i=0;imax)max=arrayij;return(max); main() int a34=1,3,5,7,2,4,6,8,15,17,19,28;printf(“max value is:%dn“, max_value(a); 运行结果如下: max value is:28,2018/11/15,C语言程序设计,4
30、0,7.6 由多个函数组成的C程序设计应用举例,【例7-14】 试编程利用海伦公式求三角形的面积 海伦公式中,如果a,b,c为三角形的三条边,令p=(a+b+c)/2,则三角形的面积s= 。 源程序如下: #include “stdio.h” #include “string.h” #include “math.h” int istriangle(int a,int b,int c); float trarea(int a,int b,int c); int istriangle(int a,int b,int c); if(a+bc ,2018/11/15,C语言程序设计,41,float trarea(int a,int b,int c) float p;p=(float)(a+b+c)/2;return(float)(sqrt(p*(p-a)*(p-b)*(p-c); void main() int a,b,c;printf(“三角形的面积nn”);printf(“请输入三角形三条边的值:”);scanf(“%d %d %d”, ,