• 1. 第六章 函数与宏定义 §6.1 函数概念 §6.2 变量作用域和存储类型 §6.3 内部函数与外部函数 §6.4 递归函数设计和调用 §6.6 综合范例 10/25/20181
  • 2. §6.1 函数概念 C语言允许把问题设计成一个一个的模块,程序通过调用模块功能来解决问题。这些模块通常都是通过函数来实现的,又可称其为函数模块。 C语言中,函数可分为两类 :一类是由系统定义的标准函数,又称为库函数,其函数声明一般是放在系统的include目录下以.h为后缀的头文件中,如在程序中要用到某个库函数,必须在调用该函数之前用#include<头文件名>命令将库函数信息包含到本程序中。另一类函数是自定义函数 ,两种形式: 第一种:函数声明、函数调用、函数定义。 第二种:函数定义、函数调用。10/25/20182
  • 3. § 6.1.1 函数定义 函数定义的一般形式可以有两种。形式一: [存储类型符] [返回值类型符] 函数名([形参说明表]) { 函数语句体 }形式二: [存储类型符] [返回值类型型符] 函数名([形参表]) 形参说明; { 函数语句体 }10/25/20183
  • 4. 说明: 1.[存储类型符]指的是函数的作用范围,它只有两种形式:static和extern。 static说明函数只能作用于其所在的源文件,用static说明的函数又称为内部函数。 extern说明函数可被其它源文件中的函数调用,用extern说明的函数,又称为外部函数。 缺省情况为extern。 2.[返回值类型符]指的是函数体语句执行完成后,函数返回的值的类型,如int, float, char等等,若函数无返回值,则用空类型void来定义函数的返回值。 缺省情况为int型。10/25/20184
  • 5. 3.函数名由任何合法的标识符构成。建议将函数名的命名与函数内容有一定关系。4.在第一种函数定义的形式中,[形参说明表]是一系列用逗号分开的每个形参变量说明。 如:int x, int y, int z 这表示形参变量有三个:x, y, z。它们的类型都是int型。 在第二种函数定义的形式中,[形参表]是一系列用逗号分开的形参变量。如:x, y, z 5.函数语句体是放在一对花括号{ }中,由局部数据类型描述和功能实现两部分组成。 10/25/20185
  • 6. 6.函数返回语句的形式有以下两种: ①函数无返回值的情况:return; ②函数有返回值的情况:return(表达式的值); 在第②种情况下要注意“表达式的值”的类型必须与函数返回值的类型相一致。例如:求两个任意整数的绝对值的和,用函数abs_sum()实现。/*直接调用库函数来计算m和n的绝对值 */ int abs_sum(int m, int n) { return (abs(m)+abs(n)); } /*函数abs()是在头文件math.h中声明的*/ 10/25/20186
  • 7. 函数定义如下: int abs_sum(int m, int n) { if (m<0) m=-m; if(n<0) n=-n; return(m+n); }§6.1.2 函数声明和调用 一.函数的声明 函数声明的一般形式:[存储类型符] [返回值类型符] 函数名([形参说明表]); 如:int abs-sun(int m, int n);10/25/20187
  • 8. 二.函数调用 函数调用是通过函数调用语句来实现的,分两种形式:①无返回值的情况: 函数名([实参表]); ②有返回值的情况: 变量名=函数名([实参表]); 该变量名的类型必须与函数的返回值类型相同。函数调用时都会去执行函数语句中的内容,函数执行完毕后,回到函数的调用处,继续执行下面的语句。10/25/20188
  • 9. §6.1.3 函数的传值方式 函数的传值方式: 采用实参表将每一个实参的值相应地传递给每一个形参变量,形参变量在接收到实参表传过来的值时,会在内存临时开辟新的空间,以保留形参变量的值,当函数执行完毕时,这些临时开辟的内存空间会被释放,并且形参的值在函数中不论是否发生变化,都不会影响到实参变量的值的变化,这就是函数的传值方式。自定义函数在程序中的使用顺序有两种形式:① 先进行函数声明,再进行函数调用,函数定义放在函数调用之后。函数声明在函数调用之前。 ② 函数定义放在函数调用之前。10/25/20189
  • 10. 【例6-1】 编程序,通过调用函数abs-sum(),求任意两个整数的绝对值的和。/*exam6_1.c 调用函数求两整数绝对值的和*/ #include int abs_sum(int m,int n); main() { int x,y,z; scanf("%d%d",&x,&y); z=abs_sum(x,y); printf("sum is %d",z); } int abs_sum(int m,int n) { if(m<0) m=-m; if(n<0) n=-n; return m+n; }程序运行结果: 7 12 sum is 1910/25/201810
  • 11. 用传值方式调用函数时,实参也可以是函数调用语句 【例6-2】求任意三个数的绝对值的和。/*exam6_2.c 调用函数求三个整数绝对值的和*/ #include int abs_sum(int m,int n); main() { int x,y,z,sum; scanf("%d%d%d",&x,&y,&z); sum=abs_sum(abs_sum(x,y),z); printf("sum is %d",sum); } int abs_sum(int m,int n) { if(m<0) m=-m; if(n<0) n=-n; return m+n; }程序运行结果: 7 12 5 sum is 2410/25/201811
  • 12. 注意: 对于有返回值的函数,调用时若没有把它赋给某个变量,仍然是可以的,只是函数的返回值有可能会被丢失。【例6-3】 求任意两数的乘积。自定义一个函数mul(),用于求两数的乘积,程序: /*exam6_3.c 求两个数的乘积*/ #include float mul(float a,float b); main() { float x,y,z; scanf("%f %f",&x,&y); z=mul(x,y); /* ① */ x=x+10; y=y-10; mul(x,y); /* ② */10/25/201812
  • 13. x=x*2; y=y*2; printf("z=%f,mul(%f,%f)=%f\n",z,x,y,mul(x,y)); /* ③ * / } float mul(float a,float b) { return a*b; }程序运行结果: 5 6 z=30.000000,mul(30.000000,-8.000000)=-240.00000010/25/201813
  • 14. 程序说明: 注释①处调用函数后的返回值赋给变量z。 注释②处调用函数后的返回值没有赋给任何变量,函数的返回值被丢失。 注释③处调用函数后的返回值成为了printf()函数的参数。10/25/201814
  • 15. §6.2 变量作用域和存储类型 一.变量的作用域 变量的作用域:指的是变量的有效范围,针对变量不同的作用域,可把变量分为局部变量和全局变量。局部变量:在函数内部或某个控制块的内部定义的变量为局部变量,局部变量的有效范围只限于本函数内部,退出函数,该变量自动失效。 全局变量:在函数外面定义的变量称为全局变量,全局变量的作用域是从该变量定义的位置开始,直到源文件结束。在同一文件中的所有函数都可以引用全局变量。 10/25/201815
  • 16. 局部变量和全局变量的作用域如图所示:10/25/201816
  • 17. 【例6-4】 变量作用域应用举例,阅读下面的程序,注意区分局部变量和全局变量的作用域。/*exam6_4.c 变量作用域举例*/ #include void a( void ); void b( void ); void c( void ); int x = 1; main() { int x = 5; printf("local x in outer scope of main is %d\n", x );10/25/201817
  • 18. { int x = 7; printf( "local x in inner scope of main is %d\n", x ); } printf( "local x in outer scope of main is %d\n", x ); a(); b(); c(); a(); b(); c();前三次输出结果: local x in outer scope of main is 5 local x in inner scope of main is 7 local x in outer scope of main is 510/25/201818
  • 19. printf( "local x in main is %d\n", x ); getchar(); return 0; } void a( void ) { int x = 25; printf( "\nlocal x in a is %d after entering a\n", x ); ++x; printf( "local x in a is %d before exiting a\n", x ); }10/25/201819
  • 20. void b( void ) { static int x = 50; printf( "\nlocal static x is %d on entering b\n", x ); ++x; printf( "local static x is %d on exiting b\n", x ); } void c( void ) { printf( "\nglobal x is %d on entering c\n", x ); x *= 10; printf( "global x is %d on exiting c\n", x ); }10/25/201820
  • 21. 程序运行结果: 后6次函数调用local x in a is 25 after entering a local x in a is 26 before exiting a  local static x is 50 on entering b local static x is 51 on exiting b  global x is 1 on entering c global x is 10 on exiting c  local x in a is 25 after entering a local x in a is 26 before exiting a  local static x is 51 on entering b local static x is 52 on exiting b global x is 10 on entering c global x is 100 on exiting c最后一次输出: local x in main is 510/25/201821
  • 22. 二.变量的存储类型 变量的存储类型:指的是变量的存储属性,它说明变量占用存储空间的区域。 在内存中,供用户使用的存储区由程序区、静态存储区和动态存储区三部分组成。 变量的存储类型有四种:auto型、register型、static型和extern型。auto型变量存储在内存的动态存储区。 register型变量保存在寄存器中。 static型变量和extern型变量存储在静态存储器。10/25/201822
  • 23. 局部变量的存储类型缺省值为auto型 。 全局变量的存储类型缺省值为extern型 。auto型变量和register型变量只用于定义局部变量。 static型变量即可定义成局部变量,又可定义成全局变量。 【例6-5】 设计一个函数:long fac(int n),可用来计算1~5的阶乘。分析:可在函数中定义一个static型变量,用来保存上次的计算结果。10/25/201823
  • 24. /*exam6_5.c 用static型变量保留上次阶乘的值*/ #include long fac(int n) { static int f=1; f=f*n; return f; } main() { int i; for(i=1;i<=5;i++) printf("%d!=%ld\n",i,fac(i)); }程序运行结果: 1!=1 2!=2 3!=6 4!=24 5!=120局部变量f被定义成static型的,因此,它只在该函数第1次被调用的时候初始化其值为1,以后再调用该函数时,不再进行初始化,而是使用上一次调用的值。 10/25/201824
  • 25. §6.3 内部函数与外部函数 一.内部函数 若函数的存储类型为static型,则称其为内部函数或称静态函数,它表示在同一个程序中(由多个源文件组成),该函数只能在一个文件中存在,在其它文件中不可使用。 如:static int fun-name(); 内部函数只能被其所在的源文件调用。二.外部函数 若函数的存储类型定义为extern型,则称其为外部函数,它表示该函数能被其它源文件调用。函数的缺省存储类型为extern型。注意:在需要用到外部函数的文件中,其函数声明必须用extern进行说明。10/25/201825
  • 26. 例如:有两个源文件file1.c和file2.c如下所示:/* file1.c 调用外部函数*/ # include int mod(int a, int b); extern int add (int m, int n); /*外部函数声明*/ main() { int x, y, result; scanf (“%d%d”, &x, &y); result=add(x,y); /*调用外部函数*/ if (result >0) result=result-mod(x,y); printf(“result=%d\n”, result); }10/25/201826
  • 27. int mod(int a, int b) { return(a%d); } /* file2.c外部函数*/ extern int add(int m, int n) { return(m+n); }说明:1.在文件1(file1.c)中的函数声明: int mod(int a, int b); 实际上相当于:extern int mod(int a, int b);10/25/201827
  • 28. 2.在文件2(file2.c)中的函数定义: extern int add(int m, int n) { return(m+n); } 实际上相当于:int add(int m, int n) { return(m+n); }3.由多个源文件组成一个程序时,main()函数只能出现在一个源文件中。10/25/201828
  • 29. 4.多个源文件的连接方式有三种:①将各源文件分别编译成目标文件,得到多个目标文件(.obj后缀),然后用连接命令(tlink)把多个.obj文件连接起来,在Turbo c上用如下命令: tlink file1.obj+file2.obj+…+filen.obj 生成一个file1.exe的可执行文件。②建立项目文件(.prj后缀),具体操作可参阅各种C编译手册。 ③使用文件包含命令。 10/25/201829
  • 30. §6.4 递归函数设计和调用 C语言中一个函数中的语句可以是对另一个函数的调用。函数嵌套调用图例:调用过程按图中箭头所示的方向和顺序进行,属于一种线性调用关系,每次调用后,最终返回到原调用点,继续执行以下语句。 10/25/201830
  • 31. C语言中还允许在函数中调用自身,或函数之间相互调用,这种调用方式称之为递归。递归又分为直接递归调用和间接递归调用。 直接递归调用;函数直接调用自身。间接递归调用:函数互相调用对方。直接递归:int temp (int x) { int y, z; …… z=temp(y); …… }10/25/201831
  • 32. 间接递归:显然,递归有可能陷入无限递归状态,最终导致错误发生。因此,设计一个递归问题必须具备两个条件:1.后一部分与原始问题类似。 2.后一问题是原始问题的简化。10/25/201832
  • 33. 【例6-6】 编程,从键盘输入一个正整数n,求n!。n!的数字表达式为: n!=定义一个求n!的函数:long fac(int n) long fac(int n) { long result; if (n= = 0 || n= =1) result =1; else result=n*fac(n-1); return(result); } 10/25/201833
  • 34. 完整程序如下: /*exam6_6.c 用递归法求n!*/ #include long fac(int n) { long result; if(n==0||n==1) result=1; else result=n*fac(n-1); return result; } main() { int x; long f;10/25/201834
  • 35. scanf("%d",&x); if(x<=0) printf("Your input is wrong!\n"); else { f=fac(x); printf("%d!=%ld\n",x,f); } }程序运行结果: 6 6!=72010/25/201835
  • 36. 【例6-7】 求Fibonacci数列第i项的值。 Fibonacci数列:0, 1, 1, 2, 3, 5, 8, 13, 21,…其数字表达式为: fibonacci(0)=0 fibonacci(1)=1 fibonacci(n)=fibonacci(n-1)+fibonacci(n-2) (n>1)设计一个函数:long fibonacci (int n)用于计算数列中第n项的值, 10/25/201836
  • 37. 程序如下所示: /*exam6_7.c 求第n项Fibonacci数列的值*/ #include long fibonacci(int n); main() { int x=0; long result; do { result=fibonacci(x); printf("fibonacci(%d)=%ld\n",x,result); scanf("%d",&x); }while(x!=-1); }10/25/201837
  • 38. long fibonacci(int n) { if(n==0||n==1) return n; else return fibonacci(n-1)+fibonacci(n-2); }程序运行结果: fibonacci(0)=0 3 fibonacci(3)=2 4 fibonacci(4)=3 610/25/201838
  • 39. 以x=4为例,下图说明了fibonacci函数是怎样计算fibonacci(4)的。图中把fibonacci简写成f。10/25/201839
  • 40. §6.6 综合范例【例6-12】 在屏幕上画一个18×18大小的棋盘。程序如下: /*exam6_12.c 在屏幕上画一个棋盘*/ #include #include #include /*定义画棋盘所需的制表符*/ #define LU 0xda /*左上角*/ #define RU 0xbf /*右上角*/ #define LD 0xc0 /*左下角*/ #define RD 0xd9 /*右下角*/ #define L 0xc3 /*左边*/ #define R 0xb4 /*右边*/10/25/201840
  • 41. #define U 0xc2 /*上边*/ #define D 0xc1 /*下边*/ #define CROSS 0xc5 /*十字叉*/ /*棋盘左上角坐标*/ #define MAP_X 5 #define MAP_Y 5 void draw_cross(int x,int y); void draw_map(); main() { textmode(C40); draw_map(); }10/25/201841
  • 42. /*函数定义:*/ void draw_map() /*画棋盘*/ { int i,j; for(i=0;i<19;i++) for(j=0;j<19;j++) draw_cross(i,j); } void draw_cross(int x,int y) { gotoxy(x+MAP_X,y+MAP_Y); textcolor(GREEN); if(x==0 && y==0) { putch(LU); /*画左上角*/ return; }10/25/201842
  • 43. if(x==0 && y==18) { putch(LD); /*画左下角*/ return; } if(x==18 && y==0) { putch(RU); /*画右上角*/ return; } if(x==18 && y==18) { putch(RD); /*画右下角*/ return; }10/25/201843
  • 44. if(x==0) { putch(L); /*画左边*/ return; } if(x==18) { putch(R); /*画右边*/ return; } if(y==0) { putch(U); /*画上边*/ return; }10/25/201844
  • 45. if(y==18) { putch(D); /*画下边*/ return; } putch(CROSS); /*画十字叉*/ }程序中宏定义的制表符的值可从ASCⅡ码表中查得,如:0xbf为十六进制表示,它代表右上角制表符“┐”。该程序必须在纯DOS模式下运行,其运行结果是在屏幕上显示一个绿色的18×18大小的方格棋盘, 如后图所示。10/25/201845
  • 46. 该程序显示一个绿色的18×18大小的方格棋盘10/25/201846
  • 47. 【例6-13】 将一个字符串的字符反向输出到屏幕。采用递归函数调用方法,程序如下:/*exam6_13.c 反向显示字符串*/ #include #include void backwards(char s[],int index); /* 函数声明*/ main() { char str[80];/*定义字符数组*/ int index = 0; strcpy(str,"Show this string."); /*字符串拷贝*/ backwards(str,index); /*函数调用*/ }10/25/201847
  • 48. void backwards(char s[],int index) /*函数定义*/ { if (s[index]) { printf("%c",s[index]); /*输出字符*/ backwards(s,index+1); /* 递归调用*/ printf("%c",s[index]); /* 输出字符*/ } }程序运行结果: Show this string..gnirts siht wohS10/25/201848
  • 49. 小结 :介绍了函数的定义和传值调用函数的使用方法 。注意:若用全局变量作为函数的参数,则在函数中可以使得该全局变量的值发生变化。对于递归函数的设计一定要有可使递归结束的条件,否则会使程序产生无限递归。使用预处理命令时,要注意以下几点: 1.宏替换定义的末尾不能使用分号“;”。 2.在有参数的宏定义中,参数加括号和不加括号有时会有区别。 3.使用文件包含时,要避免出现变量和函数发生重定义的现象。 4.要区分条件编译与条件语句的作用。10/25/201849