1、第六章 函数与预编译处理,教学目的要求: 1、掌握函数的一般概念和使用的时机 2、了解模块化程序设计的基本概念3、掌握标准函数库的使用4、掌握函数定义的形式、调用以及参数和参数传递 5、掌握编译预处理,尤其是宏定义6、能灵活运用函数进行编程 重点难点:1、模块化的概念及其设计原则2、标准函数的使用方法3、函数的概念、定义的形式、参数的传递以及实参、形参的区别4、编译预处理中的带参宏定义5、函数的嵌套调用,概 述,一件事情往往要实现多个功能,一个程序,多个函数,一个C程序可由一个主函数和若干个函数构成。,由主函数调用其他函数,其他函数也可以互相调用。同一个函数可以被一个或多个函数调用任意多次。,
2、i,关于函数的几点说明,1、一个源程序文件由一个或者多个函数组成。 一个源程序文件是一个编译单位,而不是以函数为单位进行编译.,2、一个C程序由一个或者多个源文件组成。,3、从用户使用的角度看,函数有两种: 标准函数和用户自己定义的函数.,4、从函数的形式看,函数有两种: 无参函数和有参函数.,05.9-14以下关于函数的叙述中正确的是 A)每个函数都可以被其它函数调用(包括main函数) B)每个函数都可以被单独编译 C)每个函数都可以单独运行 D)在一个函数内部可以定义另一个函数,(B),print_star( ) printf( “* * * * * * * * * * * * * *
3、* * * *” ); ,* * * * * * * * * * * * * * * * * How do you do! * * * * * * * * * * * * * * * * *,print_message( ) printf( “nHow do you do ! n” ); ,main( ) printf_star( ); /* 调用printstar函数 */ print_message( ); /* 调用print_message函数 */ printf_star( ); /* printstar函数 */ ,2、 有参函数的定义形式 类型标识符 函数名(形式参数表列) 声明
4、部分 语句 ,函 数 定 义 的 形 式,1、 无参函数的定义形式类型标识符 函数名() 声明部分语句,int max( int x,int y ) int z ;z = x y ? x : y ;return( z ) ; ,函数类型 函数名(参数类型 参数名, , 参数类型 参数名),print_message( ) printf( “How do you do ! n” ); ,关 于 定 义 的 说 明,1、max为函数名,括号中有两个形式参数x和 y,均是整型。在调用时,主调函数把实参的值传递给被调用函数max中的形参x和y,这时x和y才获得相应的存储单元。其本质是进行值的复制.,2
5、、花括弧内是函数体,它包括声明部分和语句部分。在声明部分定义函数中所用的变量(局部变量)。,int max( int x , int y ) int z ;z = x y ? x : y ;return( z ) ; ,3、在语句部分中求解,return(z)的作用是将z的值作为整个函数的值带回到主调函数,return后面括号中的值z就是函数带回的值即函数返回值。,4、如果在定义函数时不指定函数类型,系统会隐含指定函数类型为int型。因此上面定义的max函数左端的int可以省写。,函 数 参 数,形式参数 和 实际参数 在调用函数时,大多数情况下,主调函数和被调用函数之间有数据传递关系。这就是
6、前面提到的有参函数。,在定义函数时函数后面括弧中的变量名称为“形式参数”(简称“形参”),int max( int x , int y ),在主调函数中调用函数时,函数名后面括弧中的参数(可以是表达式)称为“实际参数”(简称“实参”), 如 c = max( a , b )。,main( ) int a,b,c; scanf(“%d,%d”, ,int max(int x,int y) int z; z=xy?x:y; return (z); ,a,b,c=max(a,b);,说 明,(5) 实参变量对形参变量的数据传递“值传递”,即单向传递,只由实参传给形参,而不能由形参传回来给实参.在内存
7、中,实参单元与形参单元是不同单元。,swap(int x,int y) int t; t=x;x=y;y=t; ,main() int a=2,b=3; if(ab)swap(a,b); printf(“a=%d,b=%d”,a,b); ,函 数 的 返 回 值,通常,希望通过函数调用使主函数能得到 一个确定的值,这就是函数的返回值。例如,例2中,max(2,3)的值是3。赋值语句将这个函数值赋值给变量c。,函数的调用属于表达式的一种, 函数调用表达式的值即为函数的返回值,main( ) int a,b,c; scanf(“%d,%d”, ,int max(int x,int y) int z
8、; z=xy?x:y; return (z); ,说 明,下面对函数值作一些说明:,1、函数的返回值是通过函数中的return语句获得的。return 语句将被调用函数中的一个确定的值带回主调函数中去。,如果需要从被调用函数带回一个函数值(供主调函数使用), 被调用函数中必需包含return语句。如:例2,如果不需要从被调用函数带回函数值可以不要return语句,如:例1 或者有return语句,但没有具体返回值,即 return ; 语句。,两种情况:,说 明,(3)returen后面的值可以是一个表达式。例如,max(int x,int y) return(xy?x:y); ,注意: (1
9、)一个函数中可以有一个以上的return语句(作为选择结构的各 个分支的内嵌语句; 不能出现顺序结构的多个return语句), 执行到哪一个return语句,哪一个语句起作用。,(2)return语后面的括弧可以不要,如return z;,如果出现多个 return 语句,且不是出现在选择结构的各个分 支中,则系统会自动执行第一个return语句,其它的不被执行,说 明,2、函数值的类型。 (1) 既然函数有返回值,这个值当然应属于某一个确定的类型,应当在定义函数时指定函数值的类型。,(2) 凡不加类型说明的函数,一律自动按int处理。,(3) 在定义函数时return语句中的表达式类型一般应
10、该和函数说 明的类型一致。,3、如果被调用函数中没有return语句,不带回值。,但实际上,函数并不是不带回值,而只是不带回有用的值,带回的是一个不确定的值。, int a,b,c; a=printstar( ); b=print_message( ); c=printstar( );printf(“%d,%d,%dn”,a,b,c); 上面的语句也是合法的,运行时除了可以得到和例1一样的结果外,还可以输出a、b、c的值。但是a、b、c的值没有实际意义。,说 明,4、为了明确表示“不带回值”,可以用void(空类型或无类型)定义函数类型。,例如,例1中的定义可以改为 void printsta
11、r( ) void print_message( ) ,这样,系统就保证不使函数带回任何值,即禁止在调用函数中使用函数的返回值。,则下面的用法就是错误的:a=printstar();b=print-message(); 编译时会给出出错信息。,05.9-30设函数fun的定义形式为 void fun(char ch, float x ) 则以下对函数fun的调用语句中,正确的是 A)fun(“abc”,3.0); B)t=fun(D,16.5); C)fun(65,2.8); D)fun(32,32); 06.4-46以下叙述中错误的是 A)C程序必须由一个或一个以上的函数组成 B)函数调用可
12、以作为一个独立的语句存在C)若函数有返回值,必须通过return语句返回 D)函数形参的值也可以传回给对应的实参,等 考 实 例,(D),(D),函 数 的 调 用,函数调用的一般形式为函数名(实参表列),如果是调用无参函数,则“实参表列”可以没有,但括弧不能省略,如果实参表列包含多个实参,则各参数间用逗号隔开。实参与形参的个数应相等,类型应一致。实参与形参按顺序对应,一 一传递数据。,函 数 调 用 的 方 式,按函数在程序中调用的位置,可以有以下三种函数调用方式: 1、函数语句:把函数调用作为一个语句。如例1中的 printstar( );这时不要求函数带回值,只要求函数完成一定的操作。,
13、2、函数表达式函数出现在一个表达式中,这种表达式称为函数表达式。这时要求函数带回一个确定的值以参加表达式的运算。例如: c=2*max(a,b);,3、函数参数函数调用作为一个函数的实参。,例如:m=max(a,max(b,c); 又如:printf(“%d”,max(a,b);,实质上是函数表达式形式调用的一种,因为函数的参数本来就要求是表达式形式。,关于函数调用的几点说明,1、C程序的执行从main函数开始,调用其它函数后,流程回到main函数。,2、所有的函数都是平行的,即在定义函数时是互相独立的,一个函数并不从属于另一个函数,即函数不能嵌套定义,函数间可以互相调用,但是不能调用main
14、函数.,3、被调用的函数必须是已经存在的函数(是库函数或用户自己定义的函数)。,4、如果使用库函数,一般还应该在本文件开头用#include命令将调用有关库函数时所需用到的信息“包含”到本文件中来。,函 数 声 明,如果使用用户自己定义的函数,而且该函数与调用它的函数(即主调函数)在同一个文件中,一般还应该在主调函数中对被调用的函数作声明,即向编译系统声明将要调用此 函数,并将有关信息通知编译系统。,函数声明 int max ( int x , int y );,在函数声明中也可以不写形参名,而只写形参的类型。 如: int max ( int , int );,函数原型的一般形式为 1、函数
15、类型 函数名(参数类型1,参数类型2),在C语言中,以上的函数声明称为函数原型。主要是利用它在程序的编译阶段对调用函数的合法性进行全面检查。,2、函数类型 函数名(参数类型1,参数名1,参数类型2,参数名2),06.4-20若各选项中所用变量已正确定义,函数fun中通过return语句返回一个函数值,以下选项中错误的程序是 A) main() x=fun(2,10); float fun(int a,int b) B) float fun(int a,int b)main() x=fun(i,j);C) float fun(int,int); main() x=fun(2,10); float
16、 fun(int a,int b)D) main() float fun(int i,int j);x=fun(i,j); float fun(int a,int b),等 考 实 例,(A),05.9-12有以下程序 int sub(int n) return (n/10+n%10); main() int x,y; scanf(“%d”, 若运行时输入:1234,程序的输出结果是 。 06.4-33有以下程序 int fun1(double a)return a*=a; int fun2(double x,double y) double a=0,b=0; a=fun1(x);b=fun1
17、(y);return(int)(a+b); main() double w;w=fun2(1.1,2.0); 程序执行后变量w中的值是 A)5.21 B)5 C)5.0 D)0.0,等 考 实 例,10,(C),函 数 的 嵌 套 调 用,函数定义都是互相平行、独立的,不能嵌套定义. 可以做嵌套调用函数, 1、在调用一个函数的过程中,又调用另一个函数。 2、在定义一个函数的过程中,调用另一个函数,main( ) printstar( ); ,printstar( ) printf(“*”);printmessage( ) ; ,printmessage( ) printf(“abcde”) ,
18、函 数 的 递 归 调 用,在调用一个函数的过程中又直接或间接的调用该函数本身,称为函数的递归调用。 例如: int f(int x)int y,z,z=f(y);return(2*z); 递归调用分为: 直接调用和间接调用,函 数 的 递 归 调 用,直接调用,int f(int x) int y,z; z=f(y); return(2*z); ,int f1(int x) int y,z; z=f2(y); return(2*z); ,int f2(int x) int y,z; z=f1(y); return(2*z); ,间接调用,程序中不应出现无终止的递归调用,而只应出现有限次数、有
19、终止 的递归调用。可以用条件语句(if)控制。,函 数 的 递 归 调 用,例5 有5个人坐在一起, 问第5个人多少岁,他说比第4个人大2岁。 问第4个人岁数,他说比第3个人大2岁。 问第3个人,又说比第2个大2岁。 问第2个人,说比第1个人大2岁。 最后问第1个人,他说他10岁。请问第5人多大?,age(5)=age(4)+2; age(4)=age(3)+2; age(3)=age(2)+2; age(2)=age(1)+2; age(1)=10;,age(n)=10;(n= 1) age(n)=age(n-1)+2;(n1),age(int n) int c; if(n= =1)c=10
20、; else c=age(n-1)+2; return ( c ); ,函 数 的 递 归 调 用,回推,递推,main() int n , k ; scanf(“%d”, ,有如下递归过程,void print(int w) int i; if(w!=0)print(w-1);for(i=1;i=w;i+) printf(“%3d”,w);printf(“n”); 调用print(4)的结果是:,1 2 2 3 3 3 4 4 4 4,06.4-39有以下程序 int fun(int n) if(n=1)return 1; else return(n+fun(n-1); main() int
21、 x; scanf(“d”, x=fun(x);printf(“dn”,x); 执行程序时,给变量X输入10,程序的输出结果是 A)55 B)54 C)65 D)45 06.4-10下面程序的运行结果是: 。 fun(int t,int n) int i,m; if(n=1) return t0; else if(n=2)m=fun(t,n-1); return m; main() int a=11,4,6,3,8,2,3,5,9,2; printf(“dn”,fun(a,10); ,等 考 实 例,(A),11,变 量 的 作 用 范 围,在一个函数内部定义的变量是内部变量,它只在本函数范围
22、内有效,也就是说只有在本函数内才能使用它们,在此函数以外是不能使用这些变量的。这称为“局部变量”。 如: float f1( int a ) /*函数f1*/,说明: 1、主函数main中定义的变量也只在主函数中有效,而不因为在主函数中定义而在;主函数也不能使用其他函数中定义的变量。,2、不同函数中可以使用相同名字的变量,它们代表不同的对象,互不干扰,它们在内存中占不同的单元 3、形式参数也是局部变量。 4、在一个函数内部,可以在复合语句中定义变量,这些变量只在 本复合语句中有效,这种复合语句也可称为“分程序”或“程序块”。,float f1(int a) int b,c; char f2(i
23、nt x,int y) int i,j;int e,f; main( ) int m,n; ,a、b、c有效,x、y、i、j有效,m、n有效,e 、 f有效,全局变量 在函数之外定义的变量称为外部变量(全局变量、全程变量), 全局变量可以为本文件中其他函数所共用。 它的有效范围为从定义变量的位置开始到本源文件结束。,变 量 的 作 用 范 围,说明: 如果在一个函数中改变了全局变量的值,就能影响到其他函数,相当于各个函数间有直接的传递通道。由于函数的调用只能带回一个返回值,因此有时可以利用全局变量增加与函数联系的渠道,从函数得到一个以上的返回值。,局部变量在函数内定义的变量是局部变量。 如:函
24、数的形参,函数内部定义的变量,int p=1,q=5; float f1(int a) int b,c; char c1,c2; char f2(int x,int y) int i,j; main( ) int m,n; ,p、q有效,c1、c2有效,int a=3,b=5; /*a、b为全局变量*/ max(int a,int b) /*a、b为局部变量*/ int c; c=ab? a:b; return (c); main( ) int a=8; /*a为局部变量*/ printf(“%d”,max (a,b); ,运行结果为:8,局部变量和全局变量同名时,全局变量在局部变量的作用范围
25、内不起作用。,变 量 的 作 用 范 围(4),全局变量的作用范围是从定义变量的位置开始到本源文件截止.如果在定义点之前的函数想引用该外部变量,则应该在该函数中用关键字extern作外部变量声明.,int main(int x,int y) int z; z=xy?x:y; return z; main( ) extern int a,b;printf(“%d”,max(a,b); int a=13,b=-8;,变 量 的 存 储 类 别,从变量存在的时间(即生存期)角度来分,可以分为静态存储方式和动态存储方式。静态存储方式是指在程序运行其间分配固定的存储空间的方式;动态存储方式是在程序运行其
26、间根据需要进行动态的分配存储空间的方式。,一个变量的完整定义应包括数据类型和存储类型: 如 static int a; 变量的存储类型有四种: auto(自动) register(寄存器) static(静态) extern(外部) 存储类型确定了所说明对象在内存中的存储位置。,根据变量的存储类别,可以知道变量的作用域和生存期。auto变量函数的局部变量,如不专门声明static存储类别,都是自动分配存储空间的,即auto变量。数据存储在动态存储区中。函数中的形参和在函数中定义的变量(包括在复合语句中定义的变量),都属此类,在调用该函数时系统会给它们分配存储空间,在函数调用结束时就自动释放这些
27、存储空间。,变 量 的 存 储 类 别,用static声明局部变量函数中的局部变量的值在函数调用结束后不消失而 保留原值,即其占用的存储单元没有释放,在下一次该函 数调用时,该变量已有值,就是上一次函数调用结束 时的值。,变 量 的 存 储 类 别(3),例staticau.c 静态变量和动态变量,以及不同的函数中重名的局部变量,例:main()int i,sum;for(i=1;i=5;i+)sum=add(i);printf(“sum=%dn”,sum);add(int a)int s=0;s=s+a;return s;,Static int s=0;,输出结果:5,输出结果:15,05.
28、9-45有以下程序 int a=2; int f(int n) static int a=3; int t=0; if(n%2) static int a=4; t += a+; else static int a=5; t += a+; return t+a+; main() int s=a, i; for( i=0; i3; i+) s+=f(i); printf(“%dn”, s); 程序运行后的输出结果是 A)26 B)28 C)29 D)24,等 考 实 例,(C),预处理是指在进行词法扫描和语法分析前所作的工作。预处理 命令是由“#”开头的行。在源程序中这些命令都放在函数外,原 文
29、件的前面,被称为预处理部分。 1、宏定义1)无参宏定义#define 标识符 字符串 说明:(1)宏定义是用宏名标识一个字符串,在宏展开时又以该字符串取代宏名,只是简单代换;(2)宏定义不是说明语句,末尾不加分号;(3)宏定义必须写在函数之外,其作用域为宏定义起到源程序结束。 (4)用引号括起来的宏名,与处理程序不对其进行宏代换,可以是常量、表达式、格式串等,定义的宏名,编 译 预 处 理,2)带参的宏定义#define 宏名(形参表) 字符串宏名中的参数称为形参,宏调用中的参数称为实参。在调用中不仅要展开宏,还要用实参替换形参。如:#define san(x) (x*x*x)若:x=3,则:
30、y=san(3)即为(3*3*3) 说明:(1)宏定义时,宏名和形参表间不能有空格;(2)宏定义的形参是标识符,实参可以是表达式;在宏调用时用实参的符号(不需计算)去替换形参,与函数形参不同不存在参数传递;如:y=san(3+2)则表达式为y=(3+2* 3+2*3+2),2、文件包含#include “文件名”#include /*仅在包含文件目录中查找*/ 功能:把指定包含的文件和当前文件的源程序文件连成一个文件。 说明: 1)一个include命令只能指定一个被包含文件,若有多个文件要包含,则须用多个include 命令; 2)文件包含允许嵌套,即在一个被包含的文件中可以包含另一个文件。,05.9-33有以下程序 # define f(x) (x*x) main() int i1, i2; i1=f(8)/f(4) ; i2=f(4+4)/f(2+2) ; printf(“%d, %dn“,i1,i2); 程序运行后的输出结果是 A)64, 28 B)4, 4 C)4, 3 D)64, 6406.4-49以下叙述中正确的是 A)预处理命令行必须位于C源程序的起始位置 B)在C语言中,预处理命令行都以“#”开头 C)每个C程序必须在开头包含预处理命令行:#include D)C语言的预处理不能实现宏定义和条件编译的功能,等 考 实 例,(C),(B),