1.41k likes | 1.66k Views
第 8 章 函数. 概述. C 语言的源程序是由函数组成的,而且至少要有一个 主函数 main ,但实用程序往往由多个函数组成,函数是 C 语言源程序的基本模块,通过对函数模块的调用实现特 定的功能。 C 语言不仅提供了极为丰富的库函数(如 printf 、 scanf 函数等),还允许用户建立自己定义的函数。用 户可把自己编写的函数编成一个个相对独立的函数模块 ,然后用调用的方法来使用函数。可以说 C 语言的全部工 作都是由各式各样的函数完成的。. 8.1 模块化设计与函数. 8.1 模块化设计与函数.
E N D
概述 • C语言的源程序是由函数组成的,而且至少要有一个 • 主函数main,但实用程序往往由多个函数组成,函数是C • 语言源程序的基本模块,通过对函数模块的调用实现特 • 定的功能。C语言不仅提供了极为丰富的库函数(如printf • 、scanf函数等),还允许用户建立自己定义的函数。用 • 户可把自己编写的函数编成一个个相对独立的函数模块 • ,然后用调用的方法来使用函数。可以说C语言的全部工 • 作都是由各式各样的函数完成的。
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(); /* 调用函数 */ • }
8.1 模块化设计与函数 • 程序运行结果: • ======================================= • Xinjiang Agricultural University • No.42 Nanchang Rd,Urumqi,Xinjiang,P.R.China • 830052 • =======================================
8.1 模块化设计与函数 • 在上述程序中引进line()函数,可以避免当需要重复打印 • 信头时,重复编写打印信头的语句。 • 在程序设计过程中,一般经常需要执行诸如此类的一些重 • 复操作。如果每次都重复编写一遍代码,程序开发效率将会很 • 低。此外,在编程处理一些比较复杂的问题时,常常需要将问 • 题细化,分解成若干个功能模块,由不同的人员分别实现,最 • 后再将这些功能模块组合起来实现最初的设计目标。 • 这些较小的、能够完成一定任务的、相对独立的程序段, • 可以被看作是组成一个程序的逻辑单元,C语言使用函数作为 • 程序的组成单元(即模块)。
8.1 模块化设计与函数 • 使用函数(模块)进行程序设计的目的如下: • (1)为了方便地使用自己编写过的代码或其他程序设计者 • 编写的代码,比如调用系统提供的库函数等,避免大量的重 • 复工作以及简化程序。 • (2)将复杂而又庞大的问题,分解成若干个较小的、功能 • 简单、相对独立但又相互关联的函数(模块)进行程序设计, • 将使程序逻辑关系明确,结构清晰,可读性好,便于调试和 • 维护。比如分别调试每一个函数(模块)的工作量将远远小于 • 调试整个程序的工作量,另外,当需要修改程序功能时,只 • 需要修改相关函数即可。
8.1 模块化设计与函数 • 在第1章介绍过,C源程序是由一个主函数和若干个其他 • 函数组成的。在C程序设计中,每一个小的功能模块可由C语 • 言的函数来完成,也就是说,C语言是通过函数来实现模块 • 化程序计的,所以较大的C语言应用程序往往是由多个函数 • 组成的,每个函数分别对应各自的功能模块。掌握C语言函 • 数的相关知识是进行模块化设计的基础。 • 本章介绍函数定义、函数调用、函数说明的概念、局部 • 函数和全局函数、局部变量和全局变量、函数调用中数据的 • 传递、变量的存储类别和递归函数等相关内容。
8.2 函数的分类 • C语言不仅提供了极为丰富的库函数,还允许用户建立 • 自己定义的函数。用户可把需要解决的问题编成一个个相对 • 独立的函数模块,然后用调用的方法来使用函数。我们可从 • 不同的角度对C语言的函数进行分类。 • 1.从函数定义的角度分类 • 从函数定义的角度看,函数可分为库函数和用户自定义函 • 数两种。 • (1)库函数。又称标准函数,C语言提供了丰富的库函数 • ,每个库函数都是一段完成特定功能的程序,由于这些功能 • 往往是程序设计人员的共同需求,所以这些功能被设计成标 • 准的程序块,并经过编译后以目标代码的形式存放在库文件 • 中。
8.2 函数的分类 • 库函数包括了常用的数学函数(如绝对值函数fabs(),平 • 方根函数sqrt()等)、字符和字符串处理函数、输入/输出函数 • 等。这些库函数由系统定义,在C程序中可以直接调用,但调 • 用之前仍需要进行函数声明。对每一类库函数,系统都提供了 • 相应的头文件,该头文件中包含了这一类库函数的声明。 • 如getchar()、putchar()等输入/输出函数的说明包含在 • stdio.h”文件中。sin()、sqrt()等数学函数的说明包含在“math.h” • 文件中,所以程序中如果要用到库函数时,在程序文件的开头 • 应使用include命令包含相应的头文件。需要说明的是,不同的 • C语言系统提供的库函数的数量和功能有所不同,但有一些基 • 本函数是相同的。
8.2 函数的分类 • (2)用户自定义函数。 • 是用户根据自己的需要编写的函数,以解决用户的专门需要。对用户自定义函数,不仅要在程序中定义函数本身,而且在主调函数模块中还必须对该被调函数进行类型说明,然后才能使用。
8.2 函数的分类 • 2.从函数是否具有返回值的角度分类 • 从这个角度看,又可把函数分为有返回值函数和无返回值 • 函数两种。 • (1)有返回值函数。此类函数被调用执行完后将向调用者返 • 回一个执行结果,成为函数返回值。如数学函数sin()、sqrt()等 • 即属于此类函数。用户定义的这种要返回值的函数,必须在函 • 数定义和函数说明中明确返回值的类型。 • (2)无返回值函数。此类函数用于完成某项特定的处理任务 • ,执行完成后不向调用者返回函数值。这类函数类似于其他语 • 言的过程。由于函数无须返回值,用户在定义此类函数时可指 • 定它的返回值为“空类型”,空类型的说明符为“void”。
8.2 函数的分类 • 3.从函数是否带有参数角度分类 • 从主调函数和被调函数之间数据传送的角度看,又可把 • 函数分为无参函数和有参函数两种。 • (1)无参函数。在函数定义、函数说明及函数调用中均不带 • 参数。主调函数和被调函数之间不进行参数传送。此类函数 • 通常用来完成一组指定的功能。可以返回或不返回函数值。 • (2)有参函数。在函数定义及函数说明时都有参数,此时 • 的参数称为形式参数(简称为形参)。在函数调用时也必须 • 给出参数,此时的参数称为实际参数(简称为实参)。进行 • 函数调用时,主调函数把实参的值传送给形参,供被调函数 • 使用。
8.2 函数的分类 • 值得注意的是,在C语言中,所有的函数定义,包括主 • 函数main()在内,都是平行的。也就是说,在一个函数的函 • 数体内,不能再定义另一个函数,即不能嵌套定义。但是函 • 数之间允许相互调用,也允许嵌套调用。习惯上把调用者称 • 为主调函数。函数还可以自己调用自己,称为递归调用。 • 主函数main()它可以调用其他函数,而不允许被其他函 • 数调用。因此,C语言程序的执行总是从main()函数开始,完 • 成对其他函数的调用后再返回到main()函数,最后由main()函 • 数结束整个程序。一个C源程序必须有,也只能有一个主函 • 数main()。
8.3 函数的定义 • C语言函数的定义形式如下: • 数据类型说明符 函数名([形参定义表]) • { 内部资料的说明 • 执行语句 • }
说明: (1)数据类型确定该函数返回值的数据类型,缺省时系统认为是整型或字符型。 (2)函数名由用户自己确定,必须符合C语言标识符的规则。 (3)形式参数(即形参变量)应是合法的标识符,形参之间用逗号隔开。函数可以没有形参,但函数名后面的一对圆括号不能缺省。 8.3 函数的定义
例如,打印信头的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 函数的定义
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 */ • }
8.3 函数的定义 • 程序运行结果: • ****************************************** • Xinjiang Agricultural University • No.42 Nanchang Rd,Urumqi,Xinjiang,P.R.China • 830052 • ******************************************
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);
8.4 函数的调用 • 调用函数的一般执行过程如下: • (1)首先计算实参表达式的值,分别传递给对应的形参。 • (2)将控制传给被调函数,开始执行被调函数。 • (3)被调用函数保存调用函数的执行现场,其中包括断点等。 • (4)执行被调函数的函数体,遇到调用其它函数,重复执行⑴ • 调用其它函数。 • (5)遇到return语句或函数体的结束括号“}”,函数执行结 • 束。控制返回调用函数,从调用语句的下一条语句开始 • 继续执行调用函数。
例8.2 求m个元素中取出n元素的组合数。 分析:这个问题三次计算阶乘,因此编写一个函数计算阶乘,主函数三次调用计算阶乘的函数,即可完成组合的计算。根据分析编写的程序如下: 8.4 函数的调用
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); }
8.4 函数的调用 • 8.4.2 函数的参数传递 • 前面已经介绍过,函数的参数分为形参和实参两种。下 • 面进一步介绍形参和实参的特点及两者的关系。 • 形参出现在函数定义中,在整个函数体内都可以用,离 • 开该函数则不能用,它主要用来接收从主调函数传递来的数 • 据。实参出现在主调函数中,进入被调函数后,实参变量也 • 不能使用。形参和实参的功能是实现数据传递,进行函数调 • 用时,主调函数把实参的值传送给被调函数的形参,从而实 • 现主调函数向被调函数的数据传送,达到被调函数从主调函 • 数接收数据的目的,从而实现数据共享。
8.4 函数的调用 • 函数的形参和实参具有以下特点: • (1)形参与实参各占独立的存储单元。但是值得注意的是,形参变量只有在被调用时才分配临时内存单元,在调用结束时,立即释放所分配的内存单元。因此,形参只有在函数内部使用,函数调用结束返回主调函数后则不能再使用该形参变量。 • (2)实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参,每一次应预先用赋值,输入等办法使实参获得确定值。
8.4 函数的调用 • (3)不仅实参的个数必须与被调函数形参的个数一致,而且实参的类型必须与被调函数对应形参的类型匹配。 • (4)函数调用中发生的数据传送是单向的,即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参,因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。
例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
8.5 函数的返回值 • 函数的值是指函数被调用后,执行函数体中的语句序列后所取得的值。函数的值只能通过return语句返回主调函数。因此,对于有返回值的函数,至少应有一个return语句。 • return语句的一般形式为: • return 表达式; • 或 • return(表达式); • return语句的作用是,结束函数的执行,并将表达式的值带回给主调函数。
例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
说明: (1)表达式的类型应与函数定义的数据类型一 致,如果不一致则以函数定义的数据类型 为准。 (2)在一个函数中允许有多个return语句,流程 执行到其中一个return时,立即返回主调函 数。 8.5 函数的返回值
#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 调用函数判断任意输入的一个自然数是否为素数。
程序运行结果: input n=17↙ 17 is a prime number. input n=27↙ 27 is not a prime number. 8.5 函数的返回值
8.5 函数的返回值 • (3)有的函数有返回值,有的函数没有返回值。有返回值的 • 函数中必须有return语句,没有返回值的函数的末尾可以 • 有一个不带表达式的return语句,或默认的return语句。 • (4)如果被调函数中没有return语句,函数不是不带回值,而 • 是带回一个不确定的值。为了明确表示函数不带回值, • 可以定义函数为无类型“void”(或称空类型)。 • 例如: • void printline() • { printf(“**********\n”); • } • printline函数的作用是输出10个“*”号,不需要返回值,因 • 此定义为void类型函数。
例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
8.6 对被调函数的声明 • C语言程序中一个函数调用另一个函数需要具备的条件是: • (1)被调用的函数必须是已经存在的函数,是库函数或用户自定义函数。 • (2)如果调用库函数,一般要在程序文件的开头用“include”命令,包含有关库函数所在的头文件。例如,程序中要用到数学函数,则一般在文件开头应写上#include <math.h>,程序中要用到输入/输出库函数,则一般在文件开头应写上#include <stdio.h>, stdio.h是一个头文件,在stdio.h文件中存放了输入/输出库函数所用到的一些宏定义信息。如果不包含stdio.h文件,就无法输入/输出库函数。.h是头文件所用的后缀,标志头文件。
8.6 对被调函数的声明 • (3)如果调用用户自定义函数,并且该函数与调用它的函数(即主调函数)在同一个程序文件中,一般还应该在主调函数中对被调函数作声明。即向编译系统声明将要调用此函数,并将有关信息通知编译。 • 与函数定义格式对应,函数的声明格式如下: • 存储类型 类型标识符 函数名(形参的定义表);
8.6 对被调函数的声明 • 以下几种情况可以不在主调函数中对被调函数进行说明: • (1)被调函数是整型(int型)或字符型(char型)函数。但使用这种方法时,系统无法对参数的类型进行检查。若调用函数时,参数使用不当,编译时也不会报错。因此,为了程序清晰和安全,建议加以声明为好。 • 例如:int max(int x,int y); • (2)被调函数写在主调函数的前面,因为被调函数写在主调函数之前,编译系统已经知道了函数的类型,并会根据函数首部提供的信息对函数作正确性检查,因此,可以不必在主调函数中对被调函数进行声明。
8.6 对被调函数的声明 • (3)在一个函数内对被调函数进行声明,与在函数外对被调函数进行声明是有区别的。如果一个函数只被另一个函数所调用,在主调函数中声明和在函数外声明等价。如果一个函数只被多个函数所调用,可以在所有函数的定义之前对被调函数进行声明,这样,在所有主调函数中就不必再对被调函数进行声明了。
例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 对被调函数的声明
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
如果把被调函数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 对被调函数的声明
函数的定义和函数的声明不是一回事。函数的定义是对函数功能的确定,包括指定函数名、函数值的类型、形式参数及其类型、函数体等,它是一个完整的、独立的程序函数单位。函数的声明的作用是把函数的名字、函数的类型及参数的类型、个数、顺序通知编译系统,以便在调用该函数时系统按此进行对照检查(函数名是否正确,实参和形参的个数、类型、顺序是否一致),只有一行命令,可以照写已定义的函数的首部再加一个分号,就是对被调函数的声明。函数的定义和函数的声明不是一回事。函数的定义是对函数功能的确定,包括指定函数名、函数值的类型、形式参数及其类型、函数体等,它是一个完整的、独立的程序函数单位。函数的声明的作用是把函数的名字、函数的类型及参数的类型、个数、顺序通知编译系统,以便在调用该函数时系统按此进行对照检查(函数名是否正确,实参和形参的个数、类型、顺序是否一致),只有一行命令,可以照写已定义的函数的首部再加一个分号,就是对被调函数的声明。 8.6 对被调函数的声明
8.7 函数的嵌套调用 • C语言中函数的定义都是相互平行、独立的,也就是说在定义函数时,一个函数内不能再定义另一个函数,即函数的定义不能嵌套。但函数的调用可以嵌套,即被调用的函数还可以再调用其它函数。
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。