1、1,C语言程序设计 (八),北京理工大学计算机学院 赵晓江 ,2,第八章 函数,8.1 C程序结构 8.2 函数定义 8.3 函数的调用与返回 8.4 在函数之间传递数据 8.5 变量的存储属性 8.6 函数的递归调用 8.7 库函数简介,3,8.1 C程序结构(1),4,8.1 C程序结构(2),C语言的特点: C语言允许将一个程序写入不同的源文件,一个源文件可以被不同的程序使用; 一个源文件可以由多个函数组成,一个函数可以被位于不同源文件中的其他函数调用; 一个C程序有且仅有一个主函数main(),主函数可以放在任何一个源文件中,程序一定从主函数开始执行; 可以通过工程文件将属于同一个程序
2、的不同源文件组装成一个程序.,5,8.1 C程序结构(3),函数是一段程序,完成特定的任务; 函数的分类 从用户的角度可分为: 库函数 用户定义函数; 从函数的形式可以分为: 有参函数 无参函数;,6,8.2 函数定义(1),类型 函数名(形式参数表) 函数内部变量说明.(执行语句) ,float func( float x , float y )float k;k=xy?pow(x,y):pow(y,x);return k; ,函数首部,函数内部变量说明,函数体,函数体,函数首部,7,8.2 函数定义(2),说明: 类型是指函数返回值的类型,函数返回值不能是数组,也不能是函数,当不指明函数类
3、型时,系统默认是整型. 函数名是用户自定义的标识符. 形参表是用逗号分隔的一组变量说明,有两种表示形式: int func(int x,int y) int func(x,y)int x,y 函数体是一段完成特定功能的程序,函数内定义的变量不能与形参同名.,8,8.2 函数定义(3),无参函数的定义: 当调用函数时,不需要向函数提供数据,则该函数成为无参函数. 无参函数一般只完成特定操作,不需要带回结果. 定义形式为:类型 函数名( )函数内部变量说明.(执行语句),9,8.2 函数定义(4),有参函数的定义: 必须遵守函数定义的一般形式. 在形参表中说明的形参,在函数体中不再需要说明,可以直
4、接使用. 函数体内部的变量说明是说明一个局部于函数体的一般变量,而形参说明则是说明一个用于函数间传送数据的形式变量. 一个函数的定义不能跨越在两个文件中.,float func( float x , float y )float k;k=xy?pow(x,y):pow(y,x);return k; ,10,8.2 函数定义(5),函数体,由若干语句组成,描述函数功能,函数体中变量要进行类型说明,空函数函数体为空,但 不能省略如: void dump( ) ,float f1( ) int f2( ).,float f1( ) int f2( ),不允许函数嵌套定义,11,8.3 函数的调用与返
5、回(1),调用: c=2+plus(2 , 7); c=8+plus(plus(1,2), 2);,调用: printa ( );,函数语句形式 (适合无返回值的函数调用),函数表达式形式(适合有返回值的函数调用),void printa( ) printf(“aaaaan”);,int plus(int a, int b) return a+b; ,一、函数调用方式,12,8.3 函数的调用与返回(2),被调用函数在调用前必须给出函数声明或函数原型,目的是便于编译程序检查形参的个数和类型。,函数类型 函数名(参数类型1 参数1,); 如: float max(int a, int b);,二
6、、函数声明与函数原型,1. 函数声明,函数定义应有函数具体的功能语句(函数体),而函数声明仅是向编译系统的一个说明,不含具体的执行动作.,函数定义只能有一次,而函数的声明可以有多次.,13,8.3 函数的调用与返回(3),在下列情况下可以省略函数声明: 当函数返回值为整型或者字符型时,可以省去函数声明. 否则当函数定义在源程序中的位置在调用该函数之前,则可以省去在调用函数中对被调用函数的函数说明. 在下列情况下必须对函数进行声明: 返回值不是整型和字符型并且函数定义在源程序中的位置在调用该函数之后; 函数的定义和调用在两个不同的文件中;,14,8.3 函数的调用与返回(4),2. 函数原型,函
7、数类型 函数名(参数类型1,参数类型2, ); 如: float max(int , int );,main( ) plus(3,4) ); ,float plus(float x, float y) return x+y;,float plus ( float x, float y ) ;,函数声明,函数原型声明,float plus(float , float );,15,8.3 函数的调用与返回(5),计算表达式的值并转换成函数类型后返回到主调函数。,1. 返回语句,return 表达式 ;或return (表达式);,return ;,返回到主调函数,无确定返回值.,形式1,形式2,三
8、、函数的返回,16,8.3 函数的调用与返回(6),int max(int a,int b) if (ab) return a;else return b; ,main( ) int x=3, y=6,z;z=max(x,y);printf(%dn,z);,一个函数中,可以有一个或多个返回语句, 但只返回一个值。,int max(int a,int b) int m;m=ab?a:b;return m; ,17,8.3 函数的调用与返回(7),print() printf(*n);,一个函数中, 可以无返回语句(void函数)。当执行到函数体末尾时,自动返回主调函数。,main()print(
9、 ); print( ); ,void,return;,18,8.3 函数的调用与返回(8),返回语句返回的值类型应与函数的数据类型一致,float plus(a, b) main() float a, b; float d; int c; d=plus(5.5 , 4.0); c=a+b; printf(“d=%.1fn”,d); return c ; 输出: d= ?,9. 0,/* c=9 */,2.函数返回值,19,8.3 函数的调用与返回(9),函数调用的执行过程:,20,8.3 函数的调用与返回(10),函数的嵌套调用:,void f1( ) f2( ); printf(“f1 t
10、o main”);,void f2( ) printf(“f2 to f1”); ,void f1( ),f2( ); main( ) f1( ); ,函数不允许嵌套定义,但允许嵌套调用,21,8.4 在函数之间传递数据(1),函数间 传递数据 的方式,参数传递:主调函数被调函数,返回函数结果:被调函数 主调函数,全局变量:作用域内函数共享,一、函数参数的传递规则,int plus(int x, int y) int d;x+; +y;d=x+y; return d;,main( ) int a=2, b=3, c;c=plus(a, b); printf(“%d,%d,%dn”,a,b,c)
11、; 输出 ?,实际参数,形式参数,2,3,7,2,3,7,22,8.4 在函数之间传递数据(2),C语言函数参数采用“值传递”的方法: 当调用函数时将实参变量的值取出来,复制给形参变量. 被调用的函数不能改变主调函数中变量的值,而只能改变它的局部的临时副本. 类似于复印操作中的复印件和原件.,23,8.4 在函数之间传递数据(3),形式参数在被调函数中定义,实际参数在主调函数中定义,1.,形参在函数调用时才临时分配存储单元,并接受来自实参的值,函数调用结束,形参的存储单元释放,其值消失。,2.,实参的值向形参进行单向值传递。,3.,实参与形参:个数相等,类型一致。 如上例, c=plus(a)
12、;,4.,y的值不确定,24,8.4 在函数之间传递数据(4),当实参之间有联系时,实参的求值顺序在不同的编译系统下是不同的,TC是从右向左. 例如: 定义函数 func(int x,int y)int k = 3;func(k,k+);等价于func(4,4);,5.,25,8.4 在函数之间传递数据(5),二、数组作为函数的参数,main( )/*输出素数*/ int k ,a9; for(k=0;k9;k+)scanf(“%d”, ,pr(int a) /*判断素数*/ int i ; for(i=2; ia; i+) if(a%i=0) return 0; return 1; ,数组元
13、素作为参数,26,8.4 在函数之间传递数据(6),数组名作为函数的参数: 实参数组和形参数组必须类型相同,形参数组可以不指明第一维的长度. 数组名代表了数组在内存中的首地址,因此数组名作为参数传递时,实参与形参之间是地址传递. 由于是地址传递,使函数中形参与实参占用同一存储区,所以形参值的改变就是实参值的改变.,27,8.4 在函数之间传递数据(7),main( ) int b5=65,35,69,59,73; max(b , 5 ); printf(“max=%dn”,b0); ,max(int a ,int n) int i,t,m=0; for(i=1; in; i+) if(amai
14、) m=i; t=a0; a0=am; am=t; ,max( )求a 数组最大值并存放在a0中,65,35,69,59,73,b,73,65,65,28,8.5 变量的存储属性(1),内存用户区:,程序区,静态数据存储区,动态数据存储区,该区变量的生存期为整个程序。 在程序运行期间,始终占用固定存储单元,变量的值始终保存。(由编译确定初值为0).,该区变量生存期为它所在的函数调用期间。调用函数时才临时分配存储单元,调用结束时,立即释放。不保存变量的值。(由程序动态定义初值),存放执行程序,变量生存期,29,8.5 变量的存储属性(2),局部变量-在函数或复合语句内部定义的变量. 其作用域在定
15、义它的函数或复合语句内部。,main( ) int a=8,b=7,c; c=plus(a,b); printf(“%d,%d,%d”, a,b,c); 输出 ?,9, 8,5 , 2,变量的作用域,变量的有效范围,int plus(int a,int b) a+; +b; /*a=9,b=8*/int a=5, b=2;/*复合语句*/ printf(“%d,%dn”,a,b);printf(“%d,%dn”,a,b);return a+b; ,8, 7 , 17,plus( )函数中的a,b、复合语句的a,b以及main( )中的a,b作用域完全不同,30,8.5 变量的存储属性(3),文
16、件1 :int x; void f2( )int x=10; printf(“f2,x=%dn”,x);,文件2:void f1( ) x+;printf(“f1,x=%d”,x); 输出 ?,main( ) x=1; f1( ); f2( ); printf(“x=%dn”,x);,/*全局变量*/,/*局部变量*/,f1,x=2,f2,x=10,extern void f1( );,全局变量在函数外定义的变量,作用域:定义点到文件尾,extern int x;/*全局变量的说明 */,x=2,31,8.5 变量的存储属性(4),在全局变量作用域内,若全局变量与局部变量同名,则在局部变量作用
17、域内全局变量不起作用。,若在作用域外使用全局变量,则必须先用extern 加以说明。,若调用其它文件的函数,则必须在主调函数中用extern对被调函数 加以说明。,32,8.5 变量的存储属性(5),变量的 存储类型,自动(auto),静态(static),寄存器(register),外部(extern),动态存储,静态存储,变量的完整定义:,存储类型 数据类型 变量名=初值,例如: auto int a,b=10;static float d,k3=1,2,3;,33,8.5 变量的存储属性(6),定义: auto int a,b; 作用域: 在定义它的函数内有效 生存期: 动态存储,出了函
18、数,变量消失.,定义: register int a,b; 作用域: 在定义它的函数内有效 生存期: 出了函数,变量消失只能定义2-3个寄存器变量,运算器,寄存器,内存,1.自动变量,2.寄存器变量,寄存器作为存储单元,存取速度快,34,8.5 变量的存储属性(7),3.静态变量,定义: static int a,b;(在函数内部定义),作用域: 在定义它的函数内有效,生存期: 静态存储, 始终存在,定义: static int a,b; (在函数外部定义),作用域: 从定义点开始直到本文件结束, 其他文件不能存取它,生存期: 静态存储, 始终存在,内部静态变量,外部静态变量,35,8.5 变
19、量的存储属性(8),int f(a) int a; auto int b=0; static int c=3; b+;c+; return a+b+c;,main( ) int c,a=1;for(c=1;c4;c+)printf(“%dn”,f(a);,输出 ?,6 (a=1,b=1,c=4)7 (a=1,b=1,c=5)8 (a=1,b=1,c=6),变量C是静态存储,占有固定存储单元,能保存变量的值。变量a、b是动态存储,不保存变量的值,36,8.5 变量的存储属性(9),定义: extern int a; (在函数外部定义) 作用域: 从定义点到本文件结束。在其他文件加extern 说
20、明, 则该文件能存取它 生存期: 静态存储,始终存在,文件1 : int a=1; static int b=5; main( ) ,文件2 : extern int a,b; c=a+b; printf(“%d,%d,%d”,a,b,c); . .,将指出文件2 中的 b 无定义。因为文件1的b是外部静态变量,不允许其他文件引用它。,4.外部 变量,37,8.6 函数的递归调用(1),在调用一个函数的过程中又出现调用该函数本身.,一、递归调用的概念,1.直接递归,函数1,函数2,2.间接递归,38,8.6 函数的递归调用(2),1 n=0 x*f(x,n-1) n0,xn=x * x n-1
21、,x * x n-2,x*x,x*1,n!=n*(n-1)!,(n-1)*(n-2)!,3*2!,2*1,xn=f(x,n)=,1 n=1 n*f(n-1) n1,n!=f(n)=,39,8.6 函数的递归调用(3),n=4 main() f(4) ,long f(n)int n; if (n=1) return 1; elsereturn n*f(n-1); ,main( ) /* n! */ int n; scanf(“%d”, ,f(4) 4*f(3) ,f(3) 3*f(2) ,f(2) 2*f(1) ,f(1) 1 ,2,6,24,二、递归程序执行过程,40,8.6 函数的递归调用(
22、4),递归和递推,long f1(int n) if (n=1) return 1; elsereturn n*f1(n-1); ,long f2(int n) int i,a100;a1 = 1;for(i=2;i=n;i+)ai=i*ai-1;return ai-1; ,41,8.6 函数的递归调用(5),1 n=0 x*f(x,n-1) n0,xn=f(x,n)=,三、数值型递归问题的编程,建立递归公式(即确定递归数学模型和 递归终止条件)。 按递归公式转换成递归程序。,float f(float x, int n) if(n=0) return 1;else return x*f(x
23、, n-1);,main() int n=5; float x=2.0;printf(%f,f(x,n); ,42,8.6 函数的递归调用(6),将问题简化到最简情况,可立即得到解答。这是递归终止条件。 将问题分解成两个子问题,其中一个极易解决,而另一个问题与原问题性质相同,只是规模缩小了。 确定由这些子问题来求解整个问题的算法。 按上述方法转换成递归程序。,四、非数值型递归问题的编程,43,8.6 函数的递归调用(7),最简情况: 1位数 , 输出它即可(递归终止条件)。 将问题分解成两个子问题:个位数和个位以前的全体数字(与原问题性质相同,只是规模缩小)。 个位以前的全体数字看作一个整体,
24、 其算法: 输出个位数字; 反向输出个位以前的各位数字。,对给定的整数,从个位向高位反向输出各位数字,123,3,12,2,1,44,8.6 函数的递归调用(8),printn(int n) if(n=0 ,1234,4,123,3,12,/* 反向输出各位数字 */ main( ) int n; scanf(“%d”,2,1,45,8.7 库函数简介,函数库:是由系统建立的具有一定功能的函数的集合。 库函数:存放在函数库中的函数,具有明确的功能、入口调用参数和返回值。 头文件:又称包含文件,在使用某一库函数时,需要使用#include来包含该函数对应的头文件。 TC的库函数分为I/O函数,字符串处理函数,数学函数,时间日期函数,动态存储分配函数等。,46,小结,函数的定义与调用,在函数之间传递数据,变量的存储属性,