1、,第七章 函数 Function,模块化程序设计的概念,7.1,函数的定义和调用,7.3,7.1 模块化程序设计的概念,在C语言中,函数分为主函数、库函数和用户自定义函数3种。程序的执行由主函数开始,然后调用其他函数,最终返回主函数并结束。,函数调用示意图,7.2 库 函 数,C语言提供了丰富的标准函数,即库函数。这类函数是由系统提供并定义好的,不必用户再去编写。用户只需要了解函数的功能,并学会正确地调用标准函数即可。,7.2.1 C语言常用库函数,对每一类库函数,在调用该类库函数时,用户在源程序的include命令中应包含该类库函数的头文件名。#include “math.h“ #inclu
2、de “stdio.h“,7.2.2 标准库函数的调用,include命令的格式为# include头文件名 或 # include “头文件名“,说明:,(1)include命令必须以#号开头,系统提供的头文件名都以.h作为后缀,头文件名用一对双引号(“”)或一对尖括号()括起来。 (2)在C语言中,调用库函数时不能缺少库函数的头文件,include命令不是语句,不能在最后加分号。,(3)两种格式的区别是:使用尖括号时,系统到存放C库函数头文件所在的目录中寻找要包含的文件,即标准方式;使用双引号时,系统先在用户当前目录中寻找要包含的文件,若找不到,再按标准方式查找。,小例子,#include
3、 /*调用strlen函数需要包含的头文件*/#include /*调用printf函数需要包含的头文件*/main() char str =“abcde“;int i;i=strlen(str); printf(“%d“,i); ,7.3 函数的定义和调用,由用户编写的函数称为自定义函数。函数必须先定义后使用。,7.3.1 函数的定义,函数定义的一般格式如下:类型说明符 函数名(类型名 形式参数1, 类型名 形式参数2, ) 函数体; ,说明:,(1)函数名是由用户命名的、唯一标识一个函数的名字。 (2)各个函数必须单独定义,不能嵌套定义,即不能在一个函数内部再定义函数。,(3)形式参数用于
4、在调用函数和被调用函数之进行数据传递,两者之间的数据类型应一致。 (4)若在函数首部省略类型名,则默认函数返回值的数据类型为int类型。,(5)在函数体中,除形参外,用到的其他变量必须在说明部分进行定义,这些变量(包括形参),只在函数调用时才临时开辟存储单元,当退出函数时,这些临时开辟的存储单元全被释放掉。因此,这种变量只在函数体内部起作用,与其他函数体中的变量互不相关,它们可以和其他函数中的变量同名。,例:求两实数之和,main() float add(float x, float y);float a,b,c;scanf (“%f%f“,,说明: (1)add是函数名,该函数返回值为flo
5、at型。 (2)形式参数为实型变量x和y,该参数接受调用本函数时实参数据的传递。 (3)函数体内的返回值是z。,自定义函数add的作用是求两实数之和,其返回值也是float型,由于定义add函数出现在调用该函数的赋值语句“c=add(a, b);”后,因此必须在调用函数中对add函数的返回值做类型说明,即 float add(float x,float y),7.3.2 函数的调用,1函数调用的一般形式函数调用的一般形式为函数名(实参表); add(a,b);实参与形参的个数应相等,类型应一致。实参与形参按顺序对应,一一传递数据。,2函数调用的方式按函数在程序中出现的位置来分,有如下3种函数调
6、用方式。(1)函数语句(2)函数表达式(3)函数参数,(1)函数语句。函数语句的调用,是指把被调函数作为一个独立的语句直接出现在主调函数中。,例如: printf(“%d %d“, i , j); max(a, b); /*调用有参函数max*/ printstr(); /*调用无参函数printstr*/这3个语句都是函数调用语句,简称函数语句。由函数语句直接调用的函数,一般不需要返回值,只要求函数完成某操作。,(2)函数表达式。必须有一个函数返回值,例如:c=5*max(a, b);,(3)函数参数。例如:main() printf(“%d“, max(a, b);,3调用函数时的注意事项
7、,(1)被调函数必须是已存在的函数。 (2)在主调函数中,要对被调函数先做声明。如果被调函数在主调函数之前出现,则在主调函数中对被调函数可以不做声明。,(3)如果被调函数的返回值为int类型,不需要在主调函数中说明。 (4)如果被调用函数的声明放在源文件的开头,则该声明对整个源文件都有效。 (5)如果被调用函数的声明是在调用函数定义的内部,则该声明仅对该调用函数有效。,(6)函数调用应该注意以下几点。 实参应在个数、类型和顺序上与形参相一致。 实参可以是常量、变量名、数组名、数组元素或表达式,即必须具有确定的值。 为了保证函数调用的正确性,在调用之前应该先弄清被调用函数的功能、输入参数、返回值
8、等,然后再进行调用。,【例7-5】编制程序,求两数的乘积。,main() float mul(); /*进行两数相乘的函数*/float x, y, z; /*定义主函数内部的局部函数*/scanf(“%f%f“, /*输出结果*/ ,float mul(float x, float y) /*函数及形参类型定义*/ float z; /*定义浮点变量*/z=x*y; /*两数相乘*/return(z); /*返回结果*/ ,7.4 函数的返回值及其类型,一个函数有其特定的功能,也有其功能所实现的结果。这一结果可以通过函数的返回值表现出来。函数的返回值通过函数体内的return语句实现。,re
9、turn语句的格式为 return 表达式; 或 return (表达式); 如果没有返回值,写为return ;,用“void”定义无返回值函数,只需在定义函数时,在函数名前加上void即可。例如: void printstr(); /*定义pritstr为无返回值函数*/ ,函数类型决定返回值的类型。类型不一致时,对于数值型数据可以自动进行类型转换。如果函数有返回值,则在函数定义和函数调用时,一般都应该指明返回值的类型。,例如: float count(int n) float s;return(s); ,在以下情况中,可以不在调用函数内对被调用函数进行类型说明: (1)当被调用函数的定义
10、位于调用函数之前时,可以不必进行类型说明 (2)函数没有返回值或返回值的类型为整型或字符型。 (3)C语言允许在所有函数的外面、文件的开头对函数的类型进行说明,这样在调用函数时就可以不对被调用函数的类型进行说明。,【例7-6】编一函数,求x的n次方的值,其中n是整数。此程序可以将x和n作为函数参数,所求结果通过return语句返回调用程序。,double power(float x, int n) int i;double pw;pw=1;for(i=1;i=n;i+)pw*=x;return(pw); ,7.5 函数调用时参数间的传递,7.5.1 变量、常量、数组元素作为函数参数形参和实参分
11、别占用不同的存储单元,这种传递方式称为“值传递”。,实参形参(单向)阅读下列程序,观察程序的运行结果。,main() int a=2, b=3, c=0;printf(“ (1)a=%d,b=%d,c=%d n“,a,b,c) ;try(a, b, c) ;printf(“ (4)a=%d,b=%d,c=%d n“, a, b, c) ; ,try(int x, int y, int z) printf(“ (2)x=%d,y=%d,z=%d n“, x, y, z) ;z=x+y; x=x*x;y=y*y;printf(“ (3)x=%d,y=%d,z=%dn“, x, y, z) ; ,运
12、行结果为 (1)a=2,b=3,c=0 (2)x=2,y=3,z=0 (3)x=4,y=9,z=5 (4)a=2,b=3,c=0,由以上输出结果可看出: (1)是在主函数中未调用try函数前执行printf函数的结果; (2)是try函数中形参值未发生变化时的结果; (3)是try函数中形参值发生变化后的结果;,(4)是try函数调用结束后返回调用函数后的结果。可以看出,形参的变化未曾影响主函数中的实参,所以输出结果(1)和(4)是完全相同的。,7.5.2 数组名作为函数参数作为实参的数组名将数组元素首地址传递给形参所表示的数组名,即实参传给形参的是地址。【例7-10】编写程序,由主函数输入字
13、符串,调用一函数使输入的字符串按反序排列,并在主函数中输出字符串。,main() void fun(); /*函数声明*/char str50;printf(“input str please:“);scanf(“%s“,str);fun(str);printf(“反序后字符串:%sn“, str) ; ,void fun(char str1) char c;int i, j; j=strlen(str1);for(i=0;ij/2;i+, j ) c=str1i ;str1i=str1j1;str1j1=c; ,运行结果为 输入:ABCDEF 输出:FEDCBA,程序说明: (1)在主调函数
14、main中,定义了数组str,并调用fun函数,数组名str作为实参。 (2)被调函数fun中,str1为形参数组名,它与实参数组名类型必须一致。,(3)调用fun函数时,只是将实参数组的首地址传递给形参数组,故实参数组与形参数组的长度可以不一致,其大小由实参数组决定。例如形参数组的定义为fun(char str1)。,7.6 函数的嵌套调用,函数的嵌套调用:在某函数体中调用了另一个函数,则在该函数被调用的过程中将发生另一次函数调用。,int f1() /*定义函数f1*/ int f2() /*定义函数f2*/ f1(); /*f2中调用函数f1*/ void main() f2(); /*
15、主函数中调用函数f2*/ ,图7-3 函数嵌套调用过程,7.7 函数的递归调用,递归调用:一个函数自己调用自己。【例7-12】用递归算法计算n! 根据数学知识,负数没有阶乘,0的阶乘为1,正整数n的阶乘为n(n1) (n2) 21可以用下列式子表示:,由以上表达式可以看出:当n 0时,求n!可以转化为求解n(n1)!的新问题,而求解(n1)!与原来求n!的方法完全相同,只是所处理的对象在递减1,由n变成了(n1)。依此类推,求(n1)!的问题又可转化为(n1)(n2)!的问题,直至所处理对象的值减至0(即n=0)时,阶乘的值为l,递归结束,不再进行下去。至此,求n!的这个递归算法结束。,总之,
16、上面的公式说明了每一循环的结果都有赖于上一循环的结果,递归总有一个“结束条件”,例如n!的结束条件为n=0。,float fac(int n) int t;if (n= =0|n= =1) t=1; else t=fac(n1)*n;return t; ,main() int n, t;printf(“enter n:“);scanf(“%d“, ,运行结果为 输入:5 输出:5!=120,7.8 局部变量和全局变量,一个函数中的语句和数据都有自己的作用域。根据作用域的不同,变量可以分为局部变量和全局变量。,7.8.1 局部变量在一个函数内部定义的变量称做局部变量,也称内部变量。作用域:本函数
17、内部,不能在其他函数中使用。,形参a,b和变量i,k有效int f1(int a, int b) int i, k;:,7.8.2 全局变量 C程序中可以在函数的外部定义变量。在函数外部定义的变量是全局变量,也称做外部变量。作用域:从定义变量的位置开始到本源文件末尾结束。全局变量可以被本文件中的多个函数共同使用。,int p=1, q=5; /*外部变量*/ f1(int a) /*定义函数*/ int b, c;char c1, c2; /*外部变量*/ f2( int a, int b) /*定义函数*/ main() int m, n ,全局变量c1,c2的作用范围,全局变量p,q的作用
18、范围,7.9 静态存储变量和动态存储变量,变量值存在的时间(即时域,亦称生存期)角度来分,分为静态存储变量和动态存储变量。,7.9.1 静态存储变量静态存储变量:指在程序运行期间分配固定的存储空间的变量。,格式为static数据类型名 变量名=初始化常数表达式 例如: static int i=5; static a10=0, 1, 2, 3, 4, 5, 6, 7, 8, 9;,说明: (1)静态存储变量的存储空间在整个程序运行期间是固定的,即使在退出静态存储变量所在的函数后,这些变量也不释放存储单元的内容。静态局部变量的生存期(即时域)一直持续到程序运行结束。,(2)静态局部变量的初值是在
19、编译时赋予的,程序运行期间不再执行。,7.9.2 动态存储变量动态存储变量:指在程序运行期间根据需要进行动态分配的存储变量,它是在程序执行的某一时刻被动态建立,而在另一时刻被动态撤销的一种变量。,7.10 内部函数和外部函数,函数根据其使用范围,可分为内部函数和外部函数。7.10.1 内部函数在定义函数的前面使用static关键字,此函数称内部函数。,例如: static float fun(float c, float y) ,说明: (1)内部函数又称为静态函数,它只能被定义它的源程序文件所调用,而不能被其他文件调用。 (2)不同文件中可有同名的内部函数,它们互不干扰。,7.10.2 外部函数在定义函数的前面加extern关键字,此函数称为外部函数。 其格式为 extern 数据类型标识符 变量名表; 例如: extern int fun(a, b),说明: (1)一个外部函数可以被其他源程序文件调用。 (2)如果在定义时省略extern,则此函数隐含为外部函数,前面程序中所使用的函数都是外部函数。 (3)当需要调用其他文件中的外部函数时,一般要用extern说明所用的函数是外部函数。,