1 / 141

第 8 章 函数

第 8 章 函数. 概述. C 语言的源程序是由函数组成的,而且至少要有一个 主函数 main ,但实用程序往往由多个函数组成,函数是 C 语言源程序的基本模块,通过对函数模块的调用实现特 定的功能。 C 语言不仅提供了极为丰富的库函数(如 printf 、 scanf 函数等),还允许用户建立自己定义的函数。用 户可把自己编写的函数编成一个个相对独立的函数模块 ,然后用调用的方法来使用函数。可以说 C 语言的全部工 作都是由各式各样的函数完成的。. 8.1 模块化设计与函数. 8.1 模块化设计与函数.

arawn
Télécharger la présentation

第 8 章 函数

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 第8章 函数

  2. 概述 • C语言的源程序是由函数组成的,而且至少要有一个 • 主函数main,但实用程序往往由多个函数组成,函数是C • 语言源程序的基本模块,通过对函数模块的调用实现特 • 定的功能。C语言不仅提供了极为丰富的库函数(如printf • 、scanf函数等),还允许用户建立自己定义的函数。用 • 户可把自己编写的函数编成一个个相对独立的函数模块 • ,然后用调用的方法来使用函数。可以说C语言的全部工 • 作都是由各式各样的函数完成的。

  3. 8.1 模块化设计与函数

  4. 8.1 模块化设计与函数 • 我们使用过由系统定义的函数printf、scanf、strcmp等。以下是我们自己编写的使用主函数调用line()函数输出一个简单的信头的程序。 • #include <stdio.h> • void line() /* 定义函数 */ • { • printf("=======================================\n"); • } • void main() • { line(); /* 调用函数 */ • printf("%s"," Xinjiang Agricultural University\n"); • printf("%s"," No.42 Nanchang Rd,Urumqi,Xinjiang,P.R.China\n"); • printf("%s\n"," 830052"); • line(); /* 调用函数 */ • }

  5. 8.1 模块化设计与函数 • 程序运行结果: • ======================================= • Xinjiang Agricultural University • No.42 Nanchang Rd,Urumqi,Xinjiang,P.R.China • 830052 • =======================================

  6. 8.1 模块化设计与函数 • 在上述程序中引进line()函数,可以避免当需要重复打印 • 信头时,重复编写打印信头的语句。 • 在程序设计过程中,一般经常需要执行诸如此类的一些重 • 复操作。如果每次都重复编写一遍代码,程序开发效率将会很 • 低。此外,在编程处理一些比较复杂的问题时,常常需要将问 • 题细化,分解成若干个功能模块,由不同的人员分别实现,最 • 后再将这些功能模块组合起来实现最初的设计目标。 • 这些较小的、能够完成一定任务的、相对独立的程序段, • 可以被看作是组成一个程序的逻辑单元,C语言使用函数作为 • 程序的组成单元(即模块)。

  7. 8.1 模块化设计与函数 • 使用函数(模块)进行程序设计的目的如下: • (1)为了方便地使用自己编写过的代码或其他程序设计者 • 编写的代码,比如调用系统提供的库函数等,避免大量的重 • 复工作以及简化程序。 • (2)将复杂而又庞大的问题,分解成若干个较小的、功能 • 简单、相对独立但又相互关联的函数(模块)进行程序设计, • 将使程序逻辑关系明确,结构清晰,可读性好,便于调试和 • 维护。比如分别调试每一个函数(模块)的工作量将远远小于 • 调试整个程序的工作量,另外,当需要修改程序功能时,只 • 需要修改相关函数即可。

  8. 8.1 模块化设计与函数 • 在第1章介绍过,C源程序是由一个主函数和若干个其他 • 函数组成的。在C程序设计中,每一个小的功能模块可由C语 • 言的函数来完成,也就是说,C语言是通过函数来实现模块 • 化程序计的,所以较大的C语言应用程序往往是由多个函数 • 组成的,每个函数分别对应各自的功能模块。掌握C语言函 • 数的相关知识是进行模块化设计的基础。 • 本章介绍函数定义、函数调用、函数说明的概念、局部 • 函数和全局函数、局部变量和全局变量、函数调用中数据的 • 传递、变量的存储类别和递归函数等相关内容。

  9. 8.2 函数的分类

  10. 8.2 函数的分类 • C语言不仅提供了极为丰富的库函数,还允许用户建立 • 自己定义的函数。用户可把需要解决的问题编成一个个相对 • 独立的函数模块,然后用调用的方法来使用函数。我们可从 • 不同的角度对C语言的函数进行分类。 • 1.从函数定义的角度分类 • 从函数定义的角度看,函数可分为库函数和用户自定义函 • 数两种。 • (1)库函数。又称标准函数,C语言提供了丰富的库函数 • ,每个库函数都是一段完成特定功能的程序,由于这些功能 • 往往是程序设计人员的共同需求,所以这些功能被设计成标 • 准的程序块,并经过编译后以目标代码的形式存放在库文件 • 中。

  11. 8.2 函数的分类 • 库函数包括了常用的数学函数(如绝对值函数fabs(),平 • 方根函数sqrt()等)、字符和字符串处理函数、输入/输出函数 • 等。这些库函数由系统定义,在C程序中可以直接调用,但调 • 用之前仍需要进行函数声明。对每一类库函数,系统都提供了 • 相应的头文件,该头文件中包含了这一类库函数的声明。 • 如getchar()、putchar()等输入/输出函数的说明包含在 • stdio.h”文件中。sin()、sqrt()等数学函数的说明包含在“math.h” • 文件中,所以程序中如果要用到库函数时,在程序文件的开头 • 应使用include命令包含相应的头文件。需要说明的是,不同的 • C语言系统提供的库函数的数量和功能有所不同,但有一些基 • 本函数是相同的。

  12. 8.2 函数的分类 • (2)用户自定义函数。 • 是用户根据自己的需要编写的函数,以解决用户的专门需要。对用户自定义函数,不仅要在程序中定义函数本身,而且在主调函数模块中还必须对该被调函数进行类型说明,然后才能使用。

  13. 8.2 函数的分类 • 2.从函数是否具有返回值的角度分类 • 从这个角度看,又可把函数分为有返回值函数和无返回值 • 函数两种。 • (1)有返回值函数。此类函数被调用执行完后将向调用者返 • 回一个执行结果,成为函数返回值。如数学函数sin()、sqrt()等 • 即属于此类函数。用户定义的这种要返回值的函数,必须在函 • 数定义和函数说明中明确返回值的类型。 • (2)无返回值函数。此类函数用于完成某项特定的处理任务 • ,执行完成后不向调用者返回函数值。这类函数类似于其他语 • 言的过程。由于函数无须返回值,用户在定义此类函数时可指 • 定它的返回值为“空类型”,空类型的说明符为“void”。

  14. 8.2 函数的分类 • 3.从函数是否带有参数角度分类 • 从主调函数和被调函数之间数据传送的角度看,又可把 • 函数分为无参函数和有参函数两种。 • (1)无参函数。在函数定义、函数说明及函数调用中均不带 • 参数。主调函数和被调函数之间不进行参数传送。此类函数 • 通常用来完成一组指定的功能。可以返回或不返回函数值。 • (2)有参函数。在函数定义及函数说明时都有参数,此时 • 的参数称为形式参数(简称为形参)。在函数调用时也必须 • 给出参数,此时的参数称为实际参数(简称为实参)。进行 • 函数调用时,主调函数把实参的值传送给形参,供被调函数 • 使用。

  15. 8.2 函数的分类 • 值得注意的是,在C语言中,所有的函数定义,包括主 • 函数main()在内,都是平行的。也就是说,在一个函数的函 • 数体内,不能再定义另一个函数,即不能嵌套定义。但是函 • 数之间允许相互调用,也允许嵌套调用。习惯上把调用者称 • 为主调函数。函数还可以自己调用自己,称为递归调用。 • 主函数main()它可以调用其他函数,而不允许被其他函 • 数调用。因此,C语言程序的执行总是从main()函数开始,完 • 成对其他函数的调用后再返回到main()函数,最后由main()函 • 数结束整个程序。一个C源程序必须有,也只能有一个主函 • 数main()。

  16. 8.3 函数的定义

  17. 8.3 函数的定义 • C语言函数的定义形式如下: • 数据类型说明符 函数名([形参定义表]) • { 内部资料的说明 • 执行语句 • }

  18. 说明: (1)数据类型确定该函数返回值的数据类型,缺省时系统认为是整型或字符型。 (2)函数名由用户自己确定,必须符合C语言标识符的规则。 (3)形式参数(即形参变量)应是合法的标识符,形参之间用逗号隔开。函数可以没有形参,但函数名后面的一对圆括号不能缺省。 8.3 函数的定义

  19. 例如,打印信头的line()函数没有形参,而且也没有返回值。例如,打印信头的line()函数没有形参,而且也没有返回值。 void line() { printf("=======================================\n"); } 例如,已知三角形三边长a,b,c,求三角形面积的函数,有3个形参,形参的类型为单精度型实数,此函数返回一个单精度型实数值。 float area(float a, float b, float c) { float p,s; p=(a+b+c)/2; s=sqrt(p*(p-a)*(p-b)*(p-c)); return s; } 8.3 函数的定义

  20. 8.3 函数的定义 • (4)C语言中函数与函数之间是相互独立的、平行的,不允许在一个函数 • 中再定义另一个函数,即函数的定义不允许嵌套。 • 例8.1 编写一个函数,输出由指定数量的指定字符组成的分隔条。 • #include <stdio.h> • #include <string.h> • void line(char c,int n) • { int i; • for(i=1;i<=n;i++) /* 循环n次 */ • putchar(c); /* 输出n个字符 */ • printf("\n"); • } • void main() • { line('*',47); /* 传送'*'字符以及输出的个数47 */ • printf("%s"," Xinjiang Agricultural University\n"); • printf("%s"," No.42 Nanchang Rd,Urumqi,Xinjiang,P.R.China\n"); • printf("%s\n"," 830052"); • line('*',47); /* 传送'*'字符以及输出的个数47 */ • }

  21. 8.3 函数的定义 • 程序运行结果: • ****************************************** • Xinjiang Agricultural University • No.42 Nanchang Rd,Urumqi,Xinjiang,P.R.China • 830052 • ******************************************

  22. 8.4 函数的调用

  23. 8.4 函数的调用 • 8.4.1 函数调用的方式 • 函数调用的一般形式为: • 函数名(实参表) • 例如,求三边长为3,4,5的三角形面积函数的调用形式为 area(3,4,5);实参可以是常量、变量、表达式及函数,各实参之间用逗号隔开函数的调用有三种方式: • (1)函数表达式。函数调用出现在表达式中。 • 如:s=area(3,4,5); • (2)函数语句。函数的调用是一个单独的语句。 • 如:printf(“I love China.\n”); • (3)函数参数。函数的调用出现在参数的位置。 • 如:max(max(a,b),c);

  24. 8.4 函数的调用 • 调用函数的一般执行过程如下: • (1)首先计算实参表达式的值,分别传递给对应的形参。 • (2)将控制传给被调函数,开始执行被调函数。 • (3)被调用函数保存调用函数的执行现场,其中包括断点等。 • (4)执行被调函数的函数体,遇到调用其它函数,重复执行⑴ • 调用其它函数。 • (5)遇到return语句或函数体的结束括号“}”,函数执行结 • 束。控制返回调用函数,从调用语句的下一条语句开始 • 继续执行调用函数。

  25. 例8.2 求m个元素中取出n元素的组合数。 分析:这个问题三次计算阶乘,因此编写一个函数计算阶乘,主函数三次调用计算阶乘的函数,即可完成组合的计算。根据分析编写的程序如下: 8.4 函数的调用

  26. void main( ) { long cmn; int m, n, t; printf(“input m,n=”); scanf(“%d,%d”,&m,&n); if(m<n) { t=m; m=n; n=t; } cmn=fac(m); cmn=cmn/fac(n); cmn=cmn/fac(m-n); printf(“cmn=%ld\n”,cmn); } 8.4 函数的调用 #include <stdio.h> long fac(int n) { long t=1; int k; for(k=2; k<=n; k++) t=t*k; return(t); }

  27. 8.4 函数的调用 • 8.4.2 函数的参数传递 • 前面已经介绍过,函数的参数分为形参和实参两种。下 • 面进一步介绍形参和实参的特点及两者的关系。 • 形参出现在函数定义中,在整个函数体内都可以用,离 • 开该函数则不能用,它主要用来接收从主调函数传递来的数 • 据。实参出现在主调函数中,进入被调函数后,实参变量也 • 不能使用。形参和实参的功能是实现数据传递,进行函数调 • 用时,主调函数把实参的值传送给被调函数的形参,从而实 • 现主调函数向被调函数的数据传送,达到被调函数从主调函 • 数接收数据的目的,从而实现数据共享。

  28. 8.4 函数的调用 • 函数的形参和实参具有以下特点: • (1)形参与实参各占独立的存储单元。但是值得注意的是,形参变量只有在被调用时才分配临时内存单元,在调用结束时,立即释放所分配的内存单元。因此,形参只有在函数内部使用,函数调用结束返回主调函数后则不能再使用该形参变量。 • (2)实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参,每一次应预先用赋值,输入等办法使实参获得确定值。

  29. 8.4 函数的调用 • (3)不仅实参的个数必须与被调函数形参的个数一致,而且实参的类型必须与被调函数对应形参的类型匹配。 • (4)函数调用中发生的数据传送是单向的,即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参,因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。

  30. 例8.3 编写一个程序,将主函数中的两个变量的值传送给swap 函数中的两个形参,交换两个形参的值。 #include <stdio.h> void swap(int x,int y) { int t; t=x; x=y; y=t; printf("x=%d, y=%d\n",x,y); } void main() { int a=10,b=20; swap(a,b); printf("a=%d, b=%d\n",a,b); } 8.4 函数的调用 程序运行结果: x=20, y=10 a=10, b=20

  31. 8.5 函数的返回值

  32. 8.5 函数的返回值 • 函数的值是指函数被调用后,执行函数体中的语句序列后所取得的值。函数的值只能通过return语句返回主调函数。因此,对于有返回值的函数,至少应有一个return语句。 • return语句的一般形式为: • return 表达式; • 或 • return(表达式); • return语句的作用是,结束函数的执行,并将表达式的值带回给主调函数。

  33. 例8.4 求一个浮点数的绝对值。 #include <stdio.h> float xabs(float x) { if(x<0) x=-x; /* 取绝对值 */ return(x); /*返回取绝对值后的x值*/ } 8.5 函数的返回值 void main( ) { float x,y; printf(“input x=”); scanf(“%f”,&x); y=xabs(x); printf(“y=%f\n”,y); } 程序运行结果: input x=-3.5↙ 3.500000

  34. 说明: (1)表达式的类型应与函数定义的数据类型一 致,如果不一致则以函数定义的数据类型 为准。 (2)在一个函数中允许有多个return语句,流程 执行到其中一个return时,立即返回主调函 数。 8.5 函数的返回值

  35. #include <stdio.h> #include <math.h> int prime(int n) { int k,m; m=sqrt(n); for(k=2;k<=m;k++) if(n%k==0) break; if(k>m) return(1); else return(0); } void main() { int n; printf(“input n=”); scanf(“%d”,&n); if(prime(n)) printf(“%d is a prime number.\n”,n); else printf(“%d is not a prime number.\n”); } 8.5 函数的返回值 例8.5 调用函数判断任意输入的一个自然数是否为素数。

  36. 程序运行结果: input n=17↙ 17 is a prime number. input n=27↙ 27 is not a prime number. 8.5 函数的返回值

  37. 8.5 函数的返回值 • (3)有的函数有返回值,有的函数没有返回值。有返回值的 • 函数中必须有return语句,没有返回值的函数的末尾可以 • 有一个不带表达式的return语句,或默认的return语句。 • (4)如果被调函数中没有return语句,函数不是不带回值,而 • 是带回一个不确定的值。为了明确表示函数不带回值, • 可以定义函数为无类型“void”(或称空类型)。 • 例如: • void printline() • { printf(“**********\n”); • } • printline函数的作用是输出10个“*”号,不需要返回值,因 • 此定义为void类型函数。

  38. 例8.6 用无类型函数输出一个整数的绝对值。 #include <stdio.h> void xabs(int x) { int y; if(x>0) y=x; else y=-x; printf("y=%d\n",y); } void main() { int n; printf("input x="); scanf("%d",&n); xabs(n); } 8.5 函数的返回值 程序运行结果: input x=-5↙ y=5

  39. 8.6 对被调函数的声明

  40. 8.6 对被调函数的声明 • C语言程序中一个函数调用另一个函数需要具备的条件是: • (1)被调用的函数必须是已经存在的函数,是库函数或用户自定义函数。 • (2)如果调用库函数,一般要在程序文件的开头用“include”命令,包含有关库函数所在的头文件。例如,程序中要用到数学函数,则一般在文件开头应写上#include <math.h>,程序中要用到输入/输出库函数,则一般在文件开头应写上#include <stdio.h>, stdio.h是一个头文件,在stdio.h文件中存放了输入/输出库函数所用到的一些宏定义信息。如果不包含stdio.h文件,就无法输入/输出库函数。.h是头文件所用的后缀,标志头文件。

  41. 8.6 对被调函数的声明 • (3)如果调用用户自定义函数,并且该函数与调用它的函数(即主调函数)在同一个程序文件中,一般还应该在主调函数中对被调函数作声明。即向编译系统声明将要调用此函数,并将有关信息通知编译。 • 与函数定义格式对应,函数的声明格式如下: • 存储类型 类型标识符 函数名(形参的定义表);

  42. 8.6 对被调函数的声明 • 以下几种情况可以不在主调函数中对被调函数进行说明: • (1)被调函数是整型(int型)或字符型(char型)函数。但使用这种方法时,系统无法对参数的类型进行检查。若调用函数时,参数使用不当,编译时也不会报错。因此,为了程序清晰和安全,建议加以声明为好。 • 例如:int max(int x,int y); • (2)被调函数写在主调函数的前面,因为被调函数写在主调函数之前,编译系统已经知道了函数的类型,并会根据函数首部提供的信息对函数作正确性检查,因此,可以不必在主调函数中对被调函数进行声明。

  43. 8.6 对被调函数的声明 • (3)在一个函数内对被调函数进行声明,与在函数外对被调函数进行声明是有区别的。如果一个函数只被另一个函数所调用,在主调函数中声明和在函数外声明等价。如果一个函数只被多个函数所调用,可以在所有函数的定义之前对被调函数进行声明,这样,在所有主调函数中就不必再对被调函数进行声明了。

  44. 例8.7 编写一个程序,比较两个数的大小。 #include <stdio.h> /* 对被调函数printf和scanf的声明 */ void max(int, int); /* 对被调函数max的声明 */ void min(int,int); /* 对被调函数min的声明 */ void print(int); /* 对被调函数print的声明 */ void main() /* 主函数的定义 */ { int a,b; printf("input a,b:"); scanf("%d,%d",&a,&b); max(a,b); min(a,b); } 8.6 对被调函数的声明

  45. void max(int x,int y) /* max函数的定义 */ { int m; m=x>y?x:y; print(m); } void min(int x,int y) /* min函数的定义 */ { int n; n=x<y?x:y; print(n); } void print(int a) /* print函数的定义 */ { printf("%d\n",a); } 8.6 对被调函数的声明 程序运行结果: input a,b=9,5↙ 9 5

  46. 如果把被调函数max、min、print分别写在主函数main()的前面,可以在前面不必对被调函数max、min、print进行声明。也可以把被调函数max、min分别写在主函数main()中,而单独把print写在主函数main()的前面。如果把被调函数max、min、print分别写在主函数main()的前面,可以在前面不必对被调函数max、min、print进行声明。也可以把被调函数max、min分别写在主函数main()中,而单独把print写在主函数main()的前面。 #include <stdio.h> /* 对被调函数printf和scanf的声明 */ void print(int); /* 对被调函数print的声明 */ void main() { void max(int, int); /* 对被调函数max的声明 */ void min(int,int); /* 对被调函数min的声明 */ int a,b; printf("input a,b:"); scanf("%d,%d",&a,&b); max(a,b); min(a,b); } 8.6 对被调函数的声明

  47. 函数的定义和函数的声明不是一回事。函数的定义是对函数功能的确定,包括指定函数名、函数值的类型、形式参数及其类型、函数体等,它是一个完整的、独立的程序函数单位。函数的声明的作用是把函数的名字、函数的类型及参数的类型、个数、顺序通知编译系统,以便在调用该函数时系统按此进行对照检查(函数名是否正确,实参和形参的个数、类型、顺序是否一致),只有一行命令,可以照写已定义的函数的首部再加一个分号,就是对被调函数的声明。函数的定义和函数的声明不是一回事。函数的定义是对函数功能的确定,包括指定函数名、函数值的类型、形式参数及其类型、函数体等,它是一个完整的、独立的程序函数单位。函数的声明的作用是把函数的名字、函数的类型及参数的类型、个数、顺序通知编译系统,以便在调用该函数时系统按此进行对照检查(函数名是否正确,实参和形参的个数、类型、顺序是否一致),只有一行命令,可以照写已定义的函数的首部再加一个分号,就是对被调函数的声明。 8.6 对被调函数的声明

  48. 8.7 函数的嵌套调用

  49. 8.7 函数的嵌套调用 • C语言中函数的定义都是相互平行、独立的,也就是说在定义函数时,一个函数内不能再定义另一个函数,即函数的定义不能嵌套。但函数的调用可以嵌套,即被调用的函数还可以再调用其它函数。

  50. 8.7 函数的嵌套调用 • 例8.8 验证哥德巴赫猜想,即一个大于等于6的偶数可以表示为两个素数之和, • 如: 6=3+3、8=3+5、10=3+7······ • 分析:目标是将一个大于等于6的偶数n,分解为n1和n2两个素数,使得n=n1+n2。采用穷举法,考虑n1和n2的所有组合情况,发现n1和n2均为素数时,即验证成功。fenjie函数用于将形参分解为两个素数之和。prime函数用于判断形参是否为素数,如果形参为素数则返回1,否则返回0。

More Related