《5函数与程序结构.ppt》由会员分享,可在线阅读,更多相关《5函数与程序结构.ppt(45页珍藏版)》请在三一文库上搜索。
1、第5章 函数与程序结构,本章要点 1. C程序的一般结构,函数的定义方法及函数的类型和返回值。 2. 函数实参与形参的对应关系,以及参数传递的方式。 3. 函数的正确调用,嵌套与递归调用。 4. 局部变量和全局变量的概念和使用方法。 5. 变量的存储类别(自动、静态、寄存器、外部)、变量的作用域和生存期。 6. 宏定义及文件包含处理的方法。 本章难点 1. 函数参数的传递。 2. 函数递归调用的执行过程。 3. 变量的作用域和生存期。,5.1 C程序的一般结构,5.1.1 模块化程序设计 自顶向下,逐步细化,5.1.2 C程序的一般结构,函数是构成C语言程序的基本功能模块,是一段程序,它完成一
2、项相对独立的任务。 一个较大的程序一般应分为若干个程序模块,每一个模块用来实现一个特定的功能。在c语言中,用函数来实现模块的功能。一个c程序由一个主函数和若干个函数组成,由主函数调用其他函数,其他函数之间也可以相互调用。同一个函数可以被一个或多个函数调用任意多次。,特点:, 一个源文件程序由一个或多个函数以及其他有关内容(如命令行、数据定义等)组成。 一个c程序由一个或多个程序模块组成,每一个程序模块作为一个源程序文件。 C程序的执行总是从主函数开始,又从主函数结束,其他函数只有通过调用关系发生作用。 一个c程序有且仅有一个主函数main()。 所有的函数在定义时是相互独立的,一个函数并不从属
3、于另一函数,即函数不能嵌套定义,不过函数之间可以相互调用,但不能调用main函数。 不同源文件的组装可以通过工程文件实现。,函数的分类,1. 从用户使用的角度看,函数有两种: (1) 标准函数,即库函数 这是由系统提供的,用户不必自己定义这些函数,可以直接使用它们。应该说明,每个系统提供的库函数的数量和功能不同,当然有一些基本的函数是共同的。 (2) 用户自己定义的函数 用以解决用户的专门需要。 2. 从函数的形式看,函数分两类: (1) 无参函数 在调用无参函数时,主调函数并不将数据传送给被调用函数,一般用来执行指定的一组操作。无参函数可以带回或不带回函数值,但一般以不带回函数值的居多。 (
4、2) 有参函数 在调用函数时,在主调函数和被调用函数之间有参数传递,也就是说,主调函数可以将数据传给被调用函数使用,被调用函数中的数据也可以带回来供主调函数使用。,【例5.1】一个函数简单应用的例子。 void main() void p_star(); /*说明p_star函数*/ void p_message(); /*说明p_message函数*/ p_star(); /*调用p_staar函数*/ p_message(); /*调用p_message函数*/ p_star(); /*调用p_star函数*/ void p_star() /*定义p_star函数*/ printf(“*n
5、“); void p_message() /*定义p_message函数*/ printf(“Good morning!n“); ,5.2 函数的定义与调用,5.2.1 函数的定义 正如变量使用前应先定义一样,函数也应该先定义后使用。函数定义后,这个函数才存在,然后才能调用它。 1. 函数定义的一般形式 (1)无参函数的定义 其定义形式如下: 类型标识符 函数名( ) 说明部分 语句部分 说明:其中的类型标识符用于指定函数值的类型;若函数无返回值,应用void说明。 函数名的命名方法与标识符相同,不能和关键字、库函数名等同名 函数名后的圆括号是函数的象征,不能省略。(在函数定义时,圆括号后无分
6、号),例:void p_star( ) printf(“*n“); 说明:其中的void在TC中可以省掉。 p_message( ) printf(“Good morning!n“); ,(2)有参函数的定义 其定义形式为: 类型标识符 函数名(形式参数说明表) 说明部分 语句部分 说明: 对于有参函数,函数的参数是主调函数和被调用函数的数据通道。参数可分为形式参数(形参)和实际参数(实参)两种。,例如: int max(int x,int y) /*形式参数说明*/ int z; /*函数体中的说明部分*/ z=xy?x:y; return(z); main( ) int a=3,b=4;
7、printf(“%d”,max(a,b); return语句的作用是将z的值作为函数值带回到主调函数中。return后面的括弧中的值作为函数带回的值(或称函数返回值)。 在一个函数的函数体内,不能再定义另一个函数,即函数不能嵌套定义。,形参:定义函数时的参数,实参:调 用函数时的参数,例编写一个通用过程(函数或子程序),该过程可以实现判断一个整数是否为素数。编写主程序调用该过程,统计所有三位数中素数的个数。,int ss(int n) int flag,i; flag=1; for(i=2;i=n-1;i+) if (n%i=0) flag=0; break; return flag; mai
8、n() int gs,i; gs=0; for (i=100;i=999;i+) if(ss(i) gs+; printf(“gs=%d“,gs); ,2. 函数的返回值,函数的数据类型就是函数返回值的类型,称为函数类型。 (1)函数的返回值通过函数中的返回语句return将被调用函数中的一个确定的值带回到主调函数中去。 return语句的用法如下: return(表达式); return 表达式; return; return语句的作用: 使程序控制从被调用函数返回到主调函数中,同时把返回值带给主调函数;释放在函数的执行过程中分配的所有内存空间。,【例5.2】在屏幕上显示计算结果时,有时会因
9、为显示的速度太快,还没有看清楚结果,屏幕上的内容就已经滚出屏幕。为了解决这个问题,可以让屏幕每显示一定的行数后就自动暂停一个,待用户看清屏幕后按键盘上的任意键后。屏幕会继续显示以后的计算结果。,#include #include /*显示数字1到100,每显示20行时暂停一次*/ void pause(void) /*函数定义,函数形式参数为空*/ getchar(); void main() int i,j=0; for(i=1;i=100;i+) printf(“%dn“,i); if(+j=20) j=0; pause(); ,5.2.2 函数的调用,一般形式为: 函数名(实参表列);
10、说明: (1)调用函数时,函数名称必须与具有该功能的自定义函数名称完全一致。 (2)实际参数表中的参数(简称实参),可以是常数、变量或表达式。如果实参不止1个,则相邻实参之间用逗号分隔。 (3)实参的个数、类型和顺序,应该与被调用函数所要求的参数个数、类型和顺序一致,才能正确地进行数据传递。,(4)对实参表求值的顺序并不是确定的,有的系统按自左至右顺序求实参的值,有的系统则按自右至左顺序。 Turbo C规定是自右至左顺序求值。 main() int i=2; printf(“%d %d %d”,+i,+i,+i); 输出为: 5 4 3 为了避免这种影响,可以用: main() int i=
11、2,j,k,a; j=+i; k=+i; a=+i; printf(“%d %d %d”,j,k,a); ,按照函数在程序中出现的位置划分,调用函数方式有以下3种:,(1)函数语句 C语言中的函数可以只进行某些操作而不返回函数值,这时的函数调用作为一条独立的语句。如例8.1中的 p_star( ); (2)函数表达式 函数作为表达式的一项,出现在表达式中,以函数返回值参与表达式的运算。这种方式要求函数是有返回值的。例如 m=5*max(a,b); (3)函数实参 函数作为另一个函数调用的实际参数出现。这种情况是把该函数的返回值作为实参进行传送,因此要求该函数必须是有返回值的。 printf(“
12、%d“,max(a,b);,函数调用的执行过程,main() int n; scanf(“%d“, ,int ss(int n) int flag,i; flag=1; for(i=2;i=n-1;i+) if (n%i=0) flag=0; break; return flag; ,1,2,3,4,5,其中的2称为函数调用;4称为函数返回。,2. 对被调用函数的说明和函数原型,在调用自定义函数之前,应对该函数(称为被调用函数)进行说明,其般格式如下: 函数类型 函数名(数据类型1 参数名1, 数据类型2 参数名2); 语言同时又规定,在以下两种情况下,可以省去对被调用函数的说明。 (1)函数
13、的返回值是整型或字符型,可以不必进行说明,系统对它们自动按整型说明。但为清晰起见,建议都加以说明为好。 (2)被调用函数的函数定义出现在调用函数之前时。因为在调用之前,编译系统已经知道了被调用函数的函数类型、参数个数、类型和顺序。 所以,养成这样的习惯:先定义子函数,最后写主函数。这样的话,可以省掉所有的函数说明。,注意: 函数的“定义”和“说明”是两个不同的内容。 “定义”是指对函数功能的确立,包括指定函数名,函数返回值类型、形参及其类型、函数体等,它是一个完整的、独立的函数单位。在一个程序中,一个函数只能被定义一次,而且是在其他任何函数之外进行。 而 “说明”(有的书上也称为“声明”)则是
14、把函数的名称、函数返回值的类型、参数的个数、类型和顺序通知编译系统,以便在调用该函数时系统对函数名称正确与否、参数的类型、数量及顺序是否一致等进行对照检查。在一个程序中,除上述可以缺省函数说明的情况外,所有调用函数都必须对被调用函数进行说明,而且是在调用函数的函数体内进行。 对库函数的调用不需要再作说明,但必须把该函数的头文件用#include命令包含在源文件前部。,5.2.3 函数的参数传递,在调用函数时,大多数情况下,主调函数和被调用函数之间有数据传递关系。 形参出现在函数定义中,在整个函数体内都可以使用,离开该函数则不能使用。 实参出现在主调函数中,进入被调函数后,实参变量也不能使用。
15、发生函数调用时,主调函数把实参的值传送给被调函数的形参从而实现主调函数向被调函数的数据传送。 在c语言中,实参向形参传送数据的方式是“值传递”。 值传递的优点就在于:被调用的函数不可能改变调用函数中变量的值,而只能改变它的局部的临时副本。这样就可以避免被调用函数的操作对调用函数中的变量可能产生的副作用。,调用过程如下: (1)给形参分配内存空间; (2)将实参a的值传递给形参; (3)执行函数体。给函数体内的变量分配存储空间,执行算法实现部分 函数返回的过程如下: 将返回值返回主函数, 释放函数调用过程中分配的所有内存空间, 结束函数调用,将流程控制权交给主调函数。 举例说明:本讲义17页的例
16、子。,5.3 局部变量与全局变,变量的作用域 :变量有效性的范围称变量的作用域 。 语言中的变量,按作用域范围可分为两种,即局部变量和全局变量。,5.3.1 局部变量,在一个函数或复合语句内定义的变量,称为局部变量,局部变量也称为内部变量。 局部变量仅在定义它的函数或复合语句内有效。 编译时,编译系统不为局部变量分配内存单元,而是在程序的运行中,当局部变量所在的函数被调用时,编译系统根据需要临时分配内存,函数调用结束,局部变量的空间被释放。,对局部变量的说明: (1)主函数中定义的变量只能在主函数中使用,不能在其它函数中使用。 (2)形参变量是属于被调函数的局部变量,实参变量是属于主调函数的局
17、部变量。 (3)允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,互不干扰,也不会发生混淆。例如,形参和实参的变量名都为n,是完全允许的。 (4)在复合语句中也可定义变量,其作用域只在复合语句范围内。,5.3.2 全局变量,全局变量也称为外部变量,它是在函数外部定义的变量。它不属于哪一个函数,它属于一个源程序文件。其作用域是整个源程序文件,可以被本文件中的所有函数共用。 在函数中使用全局变量,一般应作全局变量说明。 但在一个函数之前定义的全局变量,在该函数内使用可不再加以说明。,对外部变量的说明:,(1)外部变量定义必须在所有的函数之外,且只能定义一次。 (2)设全局变量
18、的作用是增加了函数间数据联系的渠道。由于同一文件中的所有函数都能引用全局变量的值,因此如果在一个函数中改变了全局变量的值,就能影响到其它函数,相当于各个函数间有直接的传递通道。 int x,y; /*定义全局变量*/ void swap( ) /*定义函数swap()*/ int t; t=x;x=y;y=t; return; void main( ) printf(“input x,y:”); scanf(“%d,%d”, ,(3)虽然外部变量可加强函数模块之间的数据联系,但是又使函数要依赖这些变量,因而使得函数的独立性降低。从模块化程序设计的观点来看这是不利的,因此在不必要时尽量不要使用全
19、局变量。 (4)在同一源文件中,允许全局变量和局部变量同名。但在局部变量的作用域内,全局变量被“屏蔽”不起作用,即局部变量优先。,5.4 变量的存储类型,1. 自动变量 2. 静态变量 (1) 静态局部变量 (2) 静态全局变量 3. 外部变量 4. 寄存器变量,5.5 函数的嵌套与递归调用,5.5.1 函数的嵌套调用 C语句不能嵌套定义函数,但可以嵌套调用函数,所谓函数的嵌套调用,是指在执行被调用函数时,被调用函数又调用另一个函数。,【例5.7】计算s=1k+2k+3k+Nk。,#include #define K 4 #define N 5 long f1(int n,int k) lon
20、g p=n; int i; for(i=1;ik;i+) p *= n; return (p); ,long f2 (int n,int k) long sum=0; int i; for(i=1;i=n;i+) sum += f1(i, k); return sum; void main() printf(“%dn“,f2(N,K); ,5.5.2 函数的递归调用,1递归函数的概念 递归是在连续执行某一个处理过程时,该过程中的某一步要用到它自身的上一步(或上几步)的结果。在一个程序中,若存在程序自己调用自己的现象就是构成了递归。 递归又分为直接递归和间接递归。 一个函数在它的函数体内直接或间
21、接地调用它自身,称为递归调用。,【例5.8】用递归法计算n!。,float ff(int n) float f; if(n0) printf(“n0,input error“); else if(n=0|n=1) f=1; else f=ff(n-1)*n; return(f); void main() int n; float y; scanf(“%d“, ,5.7 编译预处理,编译预处理是指在系统对源程序进行编译之前,对程序中某些特殊的命令行的处理,预处理程序将根据源代码中的预处理命令修改程序,使用预处理功能,可以改善程序的设计环境,提高程序的通用性、可读性、可修改性、可调试性、可移植性和
22、方便性,易于模块化。 预处理程序的位置在主函数之前,定义一次,可在程序中多处展开和调用,它的取舍决定于实际程序的需要。预处理程序一般包括:宏定义和宏替换、文件包含(又称头文件)、条件编译。 预处理命令是一种特殊的命令,为了区别一般的语句,必须以#开头,结尾不加分号。预处理命令可以放在程序中的任何位置,其有效范围是从定义开始到文件结束。,5.7.1 宏定义,宏定义可以分为符号常量(不带参数)和带参数的两种。也可以使用#undef命令终止宏定义的作用域。 1符号常量(不带参数)的宏定义 用一个指定的标识符(即名字)来代表一个字符串,其一般形式为: #define 标识符 字符串 宏定义的功能:在进
23、行编译前,用字符串原样替换程序中的标识符。 宏定义的作用: (1)便于对程序进行修改。 (2)提高源程序的可移植性。 (3)减少源程序中重复书写字符串的工作量。,【例5.10】 输入圆的半径,求圆的周长、面积和球的体积。要求使用无参宏定义圆周率。,#include #define PI 3.1415926 /*PI是宏名,3.1415926用来替换宏名的常数*/ void main() double radius,length,area,volume; printf (“Input a radius: “); scanf (“%lf“, ,对宏的说明:,(1)为了和变量名加以区别,宏名一般用大
24、写字母表示 (2)宏定义是用宏名替换一个字符串,不管该字符串的词法和语法是否正确,也不管它的数据类型,即不作任何检查。如果有错误,只能由编译程序在编译宏展开后的源程序时发现。 (3)在宏定义时,可以使用已经定义的宏名。即宏定义可以嵌套,可以层层替换。例如 #define R 3.0 #define PI 3.14159 #define L 2*PI*R #define S PI*R*R void main() printf(“L=%fnS=%fn”,L,S); 替换为 printf(“L=%fnS=%fn”, 2*PI*R, PI*R*R); printf(“L=%fnS=%fn”,2*3.1
25、4159*3.0,3.14159*3.0*3.0); (4)在程序中,用双引号括起来的宏名被认为是一般字符,并不进行替换。 #define PAI 3.1415 printf(“ PAI * r * r = %f ”,s); /*并不用3.1415替换PAI*/,2带参数的宏定义,对带参数的宏,在调用中,不是进行简单的字符串替换,还要进行参数替换。 带参宏定义的一般形式为 #define 宏名(形参表) 字符串 带参宏调用的一般形式为 宏名(实参表); 例如, #define M(y) (y)*(y)+3*(y) /*宏定义*/ k=M(5); /*宏调用*/ 在宏调用时,用实参5去替换形参y
26、,经预处理宏展开后的语句为:k=(5)*(5)+3*(5); 其中的括号是必要的。,(1)在宏定义中的形参是标识符,而宏调用中的实参可以是表达式。 #define SQ(y) (y)*(y) void main() int a,sq; printf(“input a number: n“); scanf(“%d“,(4)定义带参宏时,宏名与左圆括号之间不能留有空格。否则,编译系统将空格以后的所有字符均作为替换字符串,而将该宏视为无参宏。 (5)带参的宏和带参函数很相似,但有本质上的不同。 (6)宏定义也可用来定义多个语句,在宏调用时,把这些语句又替换到源程序内。例如, #define SSSV
27、(s1,s2,s3,v) s1=l*w;s2=l*h;s3=w*h;v=w*l*h; void main() int l=3,w=4,h=5,sa,sb,sc,vv; SSSV(sa,sb,sc,vv); printf(“sa=%dnsb=%dnsc=%dnvv=%dn“,sa,sb,sc,vv); (7)较长的定义在一行中写不下时,可在本行末尾使用反斜杠表示续行。宏替换不占运行时间,只占编译时间。而函数调用则占运行时间。,3取消宏定义(#undef),宏定义的作用范围是从宏定义命令开始到程序结束。如果需要在源程序的某处终止宏定义,则需要使用#undef命令取消宏定义。取消宏定义命令#unde
28、f的用法格式为: #undef 标识符 其中的标识符是指定义的宏名。 #define PI 3.14159 void main( ) float r=10.0; float b,c,d; b=PI*r; #undef PI /*取消了宏定义*/ c=PI*r*r; d=PI*r*r*r; printf(“r=%6.2fn”,r); ,5.7.2 文件包含,文件包含是指,一个源文件可以将另一个源文件的全部内容包含进来,即将另外的文件包含到本文件之中。C语言提供了#include命令用来实现文件包含的操作。文件包含命令行的一般形式为 include “包含文件名” 或 include 其中:(1)
29、使用双引号:包含文件名中可以包含文件路径,系统首先到当前目录下查找被包含文件,如果没找到,再到系统指定的“包含文件目录”(由用户在配置环境时设置)去查找。 (2)使用尖括号:直接到系统指定的“包含文件目录”去查找。一般地说,使用双引号比较保险。 文件包含命令的功能是把指定的文件插入该命令行位置取代该命令行,从而把指定的文件和当前的源程序文件连成一个源文件。,5.7.3 条件编译,但如果用户希望某一部分程序在满足某条件时才进行编译,否则不编译或按条件编译另一组程序,这时就要用到条件编译。 可以按不同的条件去编译不同的程序部分,因而产生不同的目标代码文件。这对于程序的移植和调试是很有用的。,5.8 程序举例,【例5.13】写两个函数,分别求两个整数的最大公约数和最小公倍数,用主函数调用这两个函数,并输出结果,两个整数由键盘输入。 【例5.14】编写一个判别素数的函数,在主函数中输入一个整数,然后输出其是否是素数的信息。,
链接地址:https://www.31doc.com/p-3411843.html