1、第八章 函数,函数的一些概念,(1)主函数、其它函数 (2)主调函数(调用其它函数的函数)、被调函数(被其它函数调用的函数) (3)标准函数(库函数)和用户自定义函数 (4)无参函数、有参函数无返回值函数、有返回值函数,函数,一个C程序可由一个main( )函数和多个其它函数构成。, 其它函数指的是:, 系统函数由系统提供,放在头文件中,自编函数由程序设计人员编写,函数,C语言中,程序是由函数来实现的,函数是C语言程序的基本单位 。 在C语言中,至少要有一个main ( )函数,程序的执行是从main( )函数开始的。函数的调用过程如图:,C 语言程序的结构,箭头表示内部调用,?我只用一个ma
2、in函数就可以编程,为什么这么复杂,还要将程序分解到函数?还要掌握这么多概念,太麻烦了! ?,从程序代码设计角度 使用函数可以控制任务的规模。 使用函数可以控制变量的作用范围。 使用函数,程序的开发可以由多人分工协作。 使用函数,可以重新利用已有的、调式好的、成熟的程序模块。 从功能设计角度,可以达到模块化设计的效果 模块化程序设计有如下特点: 模块相对独立,功能单一,可混合编写也可独立编写调试。 可集体开发,缩短开发周期。 开发出的模块,可在不同的应用程序中多次使用,减少重复劳动,提高开发效率。 测试、更新以模块为单位进行而不会影响其他模块.,不使用函数(除main外) main() int
3、 n1,n2,n3,nmax;scanf(“%d%d%d”, ,使用函数 extern int max(int,int,int); main() int n1,n2,n3,nmax;scanf(“%d%d%d”, ,比较 例:输入三个整数,求三个整数中的最大值,打印。,函数定义,像调用库函数一样调用,说明:比较两个程序,使用函数好象程序更长了,但是请思考,如果程序中要调用100次求三个数最大值又会是什么情况呢?,函数声明 (被调用之前声明),函数,每个函数相当于一个输入输出系统。,返回处理结果,若没有返回值,相当于执行特定的系列操作,输入参数(可有可无),输入参数(可有可无),函数的定义,in
4、t max(x,y)int x,y;int z;if (xy)z=x;elsez=y;return(z);,int max(int x , int y) int z;if (xy)z=x;elsez=y;return(z);,函数类型 函数名(函数参数类型1 函数参数名1,函数参数类型n 函数参数名n) 声明部分执行部分 ,函数头,函数体,存储类型符 返回值类型符 函数名(形参表列),形参说明;, 说明部分,语句,说明:,函数头 存储类型(后面讲) 数据类型:指函数返回值的类型。若缺省函数类型,一律按整型处理;如果不返回值,定义为void类型。 函数名:给函数取的名字,以后用这个名字调用 函数
5、名后面是参数表,无参函数没有参数传递,但“()”号不能省略。,函数体:函数首部下用一对括起来的部分。函数体一般包括声明部分、执行部分两部分。 声明部分:在这部分定义本函数所使用的变量和进行有关声明(如函数声明)。 执行部分:程序段,由若干条语句组成命令序列(可以在其中调用其它函数)。也可以是空函数。,注意: 函数是平等的兄弟般的关系,它不能嵌套定义即不能在一个函数体内再定义另一个函数。 只有自定义函数而没有main()函数的程序是没有意义的。,数据类型 函数名 (类型标记符 形参 , ); 注意后面的“;”不要丢了。 它与定义不同,一个函数一般要经过声明才能使用(就好象变量一样必须先声明才能使
6、用),除非它在调用它的前面定义。只有当返回的数据类型为int,数据类型才可省略。 如:double new_style ( int, double ); /函数声明main( ) new_style ( 1,1.2 ); /函数调用Double new_style (int a, double x) /函数定义,函数的声明, 变量名=函数名(实参表列);,(变量名的类型必须与函数的返回值类型相同),函数的调用,说明: 无参函数调用没有参数,但是“()”不能省略,有参函数若包含多个参数,各参数用“,”分隔,实参参数个数与形参参数个数相同,类型一致或赋值兼容。 函数调用可以出现的位置 以单独语句形
7、式调用(注意后面要加一个分号,构成语句)。 例如: printf(“max=%d”,nmax); 在表达式中调用(后面没有分号)。在表达式中的函数调用必须有返回值。 例如:if(strcmp(s1,s2)0) /函数调用strcmp()在关系表达式中。 在函数参数中调用。 例如:m=max(x,max(y,z); /函数调用max()在函数参数中。,函数调用时数据的传递 (函数之间的通讯),函数是相对独立的,但是不是孤立的,它们通过调用时 1)参数传递 2)函数的返回值 3)全局变量(后面介绍)来相互联系,函数的返回值,C语言可以从函数(被调用函数)通过return语句返回值给调用函数。使用r
8、eturn语句能够返回一个值或不返回值(此时函数类型是void)。 Return语句的格式: Return 表达式;或return (表达式); return后面表达式的类型必须和函数定义时函数名前的类型保持一致。,如:int func (int n) if (n10)return (2*x+3); /正确elsereturn; /编译时,由于第二个return语句而给出警告。,用return语句,返回函数的值。,形式参数与实际参数,1、 形式参数(形参):函数定义时设定的参数。例:函数头int max(int x,int y,int z)中x,y,z就是形参,它们的类型都是整型。2、 实际参
9、数(实参):调用函数时所使用的实际的参数。例:主函数中调用max函数的语句是:nmax=max(n1,n2,n3); 其中n1,n2,n3就是实参,它们的类型都是整型。两种参数传递方式中,实参可以是变量、常量、表达式;形参一般是变量,要求两者类型相同或赋值兼容。,参数的传递,在调用函数时,主调函数和被调函数之间有数据的传递-实参传递给形参。具体的传递方式有两种: (1)值传递方式:将实参单向传递给形参的一种方式。 (2)地址传递方式:将实参地址单向传递给形参的一种方式。 注意: (1)单向传递:不管“传值”、还是“传址”,C语言都是单向传递数据的,一定是实参传递给形参,反过来不行。 (2)“传
10、值”、“传址”只是传递的数据类型不同(传值-一般的数值,传址-地址)。传址实际是传值方式的一个特例,本质还是传值,只是此时传递的是一个地址数据值。 (3)系统分配给实参、形参的内存单元是不同的,也就是说即使在函数中修改了形参的值,也不会影响实参的值。 对于传值,即使函数中修改了形参的值,也不会影响实参的值。 对于传址,即使函数中修改了形参的值,也不会影响实参的值。但是,注意:不会影响实参的值,不等于不影响实参指向的数据。,传值调用和传引用(传址)调用,1. 传值调用,方式:函数的实参和形参的类型均为简单变量。,系统会建立一份实参的拷贝给形参。,当函数调用完毕,这份实参的拷贝消失。,特点:传值调
11、用不会影响实参的值。,例 main( ) int a=3, b=5;void swap( int , int );swap (a, b);printf(“a=%d, b=%dn”, a, b); void swap (int x, int y) int temp;temp=x; x=y; y=temp;printf(“x=%d , y=%d n”, x, y); ,参数的传递,2. 传引用(传址)调用,传递的是一个地址数据值,形参变量和实参变量共同内存地址。,特点:形参变量的值的变化会影响实参的值的改变。,函数的嵌套调用和递归调用,函数嵌套调用:函数调用中又存在调用。例如:函数1调用函数2,函
12、数又调用函数3。函数之间没有从属关系,一个函数可以被其它函数调用,同时该函数也可以调用其它函数。 函数的递归调用:是指函数直接调用或间接调用自己,或调用一个函数的过程中出现直接或间接调用该函数自身。前者称为直接递归调用,后者称为间接递归调用。例如:-f1()-f1()直接递归调用;-f1()-f2()-f1()间接递归调用。,以下表示了递归的概念,汉诺塔(Hanoi)问题,问题: 将A塔上n个盘子移至C(借助于B)。 移动时,保证三个塔始终是大盘在下,小盘在上。,A,n个盘子,必须用递归方式解决,1) 先将A塔n 1个盘子借助于C移至B上,2) 将A上剩下的一个移至C上.,3) 将B上n 1个
13、盘子借助于A移至C上.,可以看到:1)、3)为同一问题,都为n 1个盘子借助于一个空塔移至另一塔上。,#include void move (char getone,char putone) /*函数定义*/ printf(“ %c %cn ”, getone, putone); void hanoi (int n, char one, char two, char three) /*将n个盘从one借助two,移到three*/ if (n= =1) move (one, three); else hanoi (n1, one, three, two); move (one, three);
14、 /*函数调用*/ hanoi (n1, two, one, three); ,程序如下:,main ( ) int m;printf (“ input the number of diskes “ :);scanf(“ %d “, /*函数调用*/ ,input the number of diskes:3 The step to moving 3 diskes:A CA BC BA CB AB CA C,运行情况如下:,#include “stdio.h“ main() int a3,i,m;int max(int,int);printf(“Please input 3 int:n“);f
15、or(i=0;ib?a:b); ,数组元素作为函数实际参数,数组元素和简单变量具有相同的特性,因此可以作为函数的实参来出现。,形参为整形,#define N 10 main() float scoreN,avg;float average(float arrN);int i;printf(“Please input %d score:n“,N);for(i=0;iN;i+)scanf(“%f“, ,形参和实参都用数组名,数组名作为函数参数,例:有一个一唯数组score,内放10个学生的分数,求平均成绩。,形参数组名只代表一个地址,在调用时将实参数组的首地址传到形参数组名,分程序作用域(复合语句
16、作用域)局部变量 函数作用域变量的作用域文件作用域(模块作用域)全局变量 程序作用域,变量的作用域,全局变量和局部变量,1、全局变量:是在函数之外定义的变量。有效范围:是从定义它的地方开始,到整个程序结束的任何地方。,例: int p=1, q=5;float f1(a)int a;int b, c; char c1, c2;,p,q的作用范围,c1, c2的作用范围,char f2(x,y); int x, y; int i, j; main ( ) ,1. 全局变量所作用到的函数,相当于这些函数的公共变量。当一个函数对其值进行改变后,另一个函数使用该变量的值亦相应改变。好处: 函数之间值传
17、递。,2. 不要随意使用全局变量。一是始终占据内存单元;二是由于函数依赖于外部定义的变量,减少了通用性。,注意,全局变量和局部变量,2、内部变量: 在一个函数内定义的变量,只在本函数内有效,这种变量就是局部变量。,a,b的有效范围,Main() int a,b; int c;c=a+b; ,变量的存储属性,变量的存储类型有: register型:寄存器类型 auto 型:自动型(缺省的都是auto型) static 型:静态型 一、自动变量auto 数据类型 变量名=初值表达式 , ; 不作特殊说明的变量都是自动变量。它用之则建、用完即撤,仅在函数体内或复合语句内建立和有效,存储在内存的动态数
18、据区中。,main( ) int x=1;clrscr(); void prt(void);int x=3;prt( );printf(“x=%dn“,x);printf(“%dn“,x); void prt(void) int x=5;printf(“%dn“,x); ,总之,auto变量是局部变量,在赋值之前,其值是不确定的,因而,对同一函数的多次调用,值不保留,因为存储单元被释放过。,Register变量,二、register变量(寄存器变量) 与auto变量完全相同,只是速度快,存储在CPU的寄存器中。,#include “stdio.h” void m_table(void) register int i, j;for (i=1 ; i=9 ; i+)for (j=1 ; j=i ; j+) printf(“%d * %d =%d ”, j, i, j*i);putchar ( I=j)? n : t ); ,Main() void m_table( );m_table( ); ,Static变量,auto变量是在程序运行过程中建立的,是动态建立、动态撤消。而静态变量是在程序一开始就建立的,不撤消直到程序结束。因而其值具有可继承性,但它只能在本程序内使用。,结果为:1, 2, 3,一般需要保留函数上一次的调用结果时,就定义成静态变量。,