函数与预编译处理.ppt
第6章 函数与预编译处理,6.1 模块化程序设计与函数6.2 函数的定义6.3 函数的调用 6.4 标识符的作用域与存储方式6.5 编译预处理6.6 函数应用举例,6.1 模块化程序设计与函数 Module program design and Function,6.1.1 函数的概念 一个较大的程序一般可分为若干个程序模块,每一个模块用来实现一个特定的功能。高级语言中都有子程序这个概念,用子程序实现模块的功能。语言中,子程序是由函数完成的。一个程序可由一个主函数和若干个函数构成。由主函数调用其他函数,其他函数也可以互相调用。同一个函数可以被一个或多个函数调用任意多次,语言不仅提供了极为丰富的库函数,还允许用户建立自己定义的函数。用户可把自己的算法编成一个个相对独立的函数模块,然后用调用的方法来使用函数。可以说程序的全部工作都是由各式各样的函数完成的,所以也把语言称为函数式语言,编写C语言程序就是编写函数。,6.1.2 函数的分类,1)库函数(1)字符类型分类函数:用于对字符按ASCII码分类:字母,数字,控制字符,分隔符,大小写字母等。(2)转换函数:用于字符或字符串的转换;在字符量和各类数字量(整型,实型等)之间进行转换;在大、小写之间进行转换。(3)目录路径函数:用于文件目录和路径操作。(4)诊断函数:用于内部错误检测。(5)图形函数:用于屏幕管理和各种图形功能。(6)输入输出函数:用于完成输入输出功能。(7)接口函数:用于与DOS,BIOS和硬件的接口。(8)字符串函数:用于字符串操作和处理。,(9)内存管理函数:用于内存管理。(10)数学函数:用于数学函数计算。(11)日期和时间函数:用于日期,时间转换操作。(12)进程控制函数:用于进程管理和控制。(13)其它函数:用于其它各种功能。,2)用户定义函数,由用户根据需要编写的函数。对于用户自定义函数,在程序中若其位置放在调用函数的后面,则必须在调用函数中对该被调函数进行类型说明才能使用,类似于在使用变量前先说明变量类型。例如,我们要编写统计一个班每个同学所有课程的平均分程序,就可将程序分成3个模块,即输入成绩,计算平均分,输出平均分。每个模块用一个函数实现,通过主函数调用将它们连接起来。上述函数编好后就可像标准函数一样,需要时可多次使用,大大提高了编程效率。语言的函数兼有其它语言中的函数和子程序两种功能,从这个角度看,又可把函数分为有返回值函数和无返回值函数两种。,1)有返回值函数,此类函数被调用执行完后将向调用者返回一个执行结果,称为函数返回值。如数学函数即属于此类函数。由用户编写的这种要返回函数值的函数,必须在函数定义和函数说明中明确返回值的类型。,2)无返回值函数,此类函数用于完成某项特定的处理任务,执行完成后不向调用者返回函数值。这类函数类似于其它语言的子程序。由于函数无须返回值,用户在定义此类函数时可指定它的返回为“空类型”,空类型的说明符为“void”。,无参函数和有参函数,1)无参函数函数定义、函数说明及函数调用中均不带参数。主调函数和被调函数之间不进行参数传送。此类函数通常用来完成一组指定的功能,可以返回或不返回函数值。2)有参函数 有参函数也称为带参函数。在函数定义及函数说明时都有参数,称为形式参数(简称为形参)。在函数调用时也必须给出参数,称为实际参数(简称为实参)。进行函数调用时,主调函数将把实参的值传送给形参,供被调函数使用。main函数是主函数,它可以调用其它函数,而不允许被其它函数调用。因此,程序的执行总是从main函数开始,完成对其它函数的调用后再返回到main函数,最后由main函数结束整个程序。一个源程序必须有,也只能有一个主函数main。,6.2 函数的定义,函数的定义就是按照规定的格式,将一个子任务编写成一个函数。例如,编写一个求平均分的函数就是平均分函数的定义。函数是指完成一个子工作的程序模块,而完成子工作后有两种可能:(1)通过执行函数得到一个明确的执行结果,该结果需要回送到调用函数中。例如,调用求绝对值函数abs(-10),可得到具体的结果10。(2)仅完成某个功能,不需回送到执行的具体结果。例如,执行输出函数printf(“*”)的结果是显示10个*。根据上述完成的工作不同,函数定义的格式有返回确定值的和不返回结果的两种。,6.2.1 返回确定值的函数定义,1.定义 返回确定值的函数定义形式如下:,函数参数的说明,(1)返回类型:函数名前面的“类型”,被称为函数类型,它实际上是函数返回值的类型,要与retum中表达式的类型一致。(2)函数名:用户自定,符合标识符命名规则即可。(3)参教表的形式:类型 参数1,类型 参数2,。(4)参数的功能:接受调用者传过来的数据,故参数是存储单元形式。若函数不需参数,圆括号()不能省略。例如,void main()。(5)return语句的形式:return 表达式;(6)return语句功能:先计算表达式的值,再将该值作为函数运算的结果回送给调用者。如果函数类型与return语句中表达式类型不一致,系统以函数类型为准,返回时自动进行类型转换。,return返回的结果只能是一个数值,如果函数的结果有多个值,将无法通过return返回。例如对求一元二次方程的函数,就不可能用return来返回两个根。若需要向调用者回送多个结果。要采用指针的方法,指针将在后面的内容中介绍。,【例6.1】编函数求二个任意整数中的大数。,参考程序如下:int max(int x,int y)if(xy)return x;if(xy)return y;在调用函数中将两个整数分别传给x和y,通过max函数就可求出它们中的大数并通过return语句将大数回送给调用者。,【例6.2】编函数求n!,double fact(int n)int i;double f=1;for(i=1;i=n;i+)f=f*i;return f;在调用函数中传给参数 n 值,通过上述函数就可出 n 值的阶乘,利用return语句回送结果。优点:函数编程的通用性较好。,6.2.2 不返回结果的函数定义,1.定义 不返回结果的函数定义的形式如下:,若不需向调用者回送结果,则函数名前面的类型规定为void,同时在函数中也不需要return语句。如果不指定任何类型,则C语言将默认函数类型为整型int。,【例6.3】输出10个“*”。,void output()int j;for(j=1;j=10;j+)printf(*);,6.3 函数的调用,一个C程序由一个主函数和多个子函数组成。主函数可以调用其他子函数,但子函数不能调用主函数,子函数之间可以互相调用。函数的调用就是在程序中调用已经存在的、可以完成特定功能的程序段,得到预期的运算结果和功能。,6.3.1 函数调用过程,对于多个函数组成的程序,C系统首先查找main()函数,然后开始执行main(),一旦遇到子函数名时,相应子函数才被真正调用,此时主函数将暂停执行,同时自动记录了暂停的位置,系统开始进入被调子函数中,先给函数中定义的变量分配存储单元,然后执行各语句,当遇到return语句后,带回函数值并返回到主函数原暂停处,再从原来暂停的位置继续往下执行。,【例6.5】求1!+2!+3!+4!+5!。,#include/*不是语句,末尾不加分号*/int fact(int n)/*求n!函数*/int j,f=1;for(j=1;j=n;j+)f=f*j;return f;/*返回main函数*/void main()int k,sum=0;for(k=1;k=5;k+)sum=sum+fact(k);printf(sum=%dn,sum);,6.3.2 函数调用形式,主函数调用子函数(包括标准函数)或子函数互相调用,一共有3种形式:(1)用调用语句调用。函数调用作为一条独立的语句。例如:printf(”%d”,sum);通常用于void类型函数的调用。(2)用表达式调用。在表达式运算中调用函数。例如:sum=sum+fact(i);(3)作为函数参数调用。函数调用作为另一个函数的实参。例如:x=sin(45*3.1415/180);,【例6.6】编写求两个数中较大值的函数max(),并用它来求3个值中的最大值。,#includeint max(int x,int y)return xy?x:y;void main()int a,b,c,m;printf(请输入3个整数:n);scanf(%d%d%d,【例6.7】编程求全年级各班的英语4级考试平均分。,#includefloat average(int i,int n)int k,x;float sum=0;printf(请输入%d班的%d人的成绩:n,i,n);for(k=1;k=n;k+)scanf(%d,【例6.7】编程求全年级各班的英语4级考试平均分。,void main()int i,class,person;float ave;printf(请输入全年级的班级总数:);scanf(%d,6.3.3 函数提前声明,C语言中的变量和函数必须遵循先定义后引用的原则。如果被调函数的定义出现在调用函数之后,必须在调用函数中对被调函数进行提前声明。函数提前声明语句的形式如下:类型名 函数名(参数表);函数声明的功能:告知编译程序被调函数的相关信息,如函数类型、函数名,参数个数及类型等。函数声明语句实际上就是函数定义的首部加分号构成。,【例6.9】编写计算xn的程序。,#includevoid main()float x,y;int n;float power(float x,int n);/*power()函数的提前声明*/scanf(%f%d,【例6.10】重写例6.7求全年级各班的英语4级考试平均分的程序。,#includevoid main()int i,class,person;float ave;float average(int i,int n);/*提前声明*/void output(int i,float a);/*output函数位置在main之后,需提前声明*/printf(请输入全年级的班级总数:);scanf(%d,/*作为单独的语句调用函数*/,float average(int i,int n)/*求各班平均分函数*/int k,x;float sum=0;printf(请输入%d班的%d人的成绩:n,i,n);for(k=1;k=n;k+)scanf(%d,6.3.4 函数参数的传递,定义函数时函数名后括弧中的变量名为“形式参数”(简称“形参”)主调函数中调用一个函数时,函数名后面括弧中的参数(可以是一个表达式)称为“实际参数”(简称“实参”)函数间的数据传递必须通过参数完成传递和接收,即必须设计一个参数完成传数据的工作。通过实参与形参的相结合就完成了数据的传递工作。,1)实际参数(简称实参),实参由调用函数提供,其形式为:,实参可以是表达式、变量、常量、函数值。实参的功能:将调用函数中的变量或常量的数值单向复制给被调函数中的形参,实参也可以是表达式,计算机先计算该表达式,然后把结果值复制给形参。,2)形式参数(简称形参),形参就是写在被调函数首部的参数表。其形式为:,形参的功能:接收并保存实参复制过来的数值,故形参只能是变量。注意:函数内声明的变量属于局部变量,当调用函数时才给这些局部变量分配临时存储单元,它们的生命期仅限于该函数内。出了该函数,不能使用这些局部变量,因为临时存储单元中的值被释放了。形参也属于局部变量,所以在变量名前要有类型符,表示被分配存储单元后才能接收实参值。,【例6.11】编写交换两个变量值的函数swap()。,#includevoid main()int a=5,b=2;void swap(int x,int y);swap(a,b);/*a和b是实参,将其值分别复制给形参x和y*/printf(a=d,b=%d n”,a,b);void swap(int x,int y)/*x和y是形参*/int t;t=x;x=y;y=t;printf(x=d,y=%d n”,x,y);,6.3.5 函数的嵌套调用,在一个被调函数中再调用其他函数称为函数的嵌套调用。二层函数嵌套关系图,【例6.12】利用函数嵌套求1!2!3!4!5!。,分析 设计3个函数,其中:(1)求n!用fact函数。(2)求阶乘之和用sum函数,在sum函数中要调用fact函数。(3)主函数中输入n的值,调用sum函数。,#includevoid main()int n,s,sum(int n);/*sum函数在main()之后,需提前声明*/printf(input n:);scanf(%d,int sum(int n)int i,s=0,fact(int k);/*fact函数在sum()之后,需提前声明*/for(i=1;i=n;i+)s=s+fact(i);return s;int fact(int k)int i,f=1;for(i=1;i=k;i+)f=f*i;return f;,6.3.6 函数的递归调用Regressive call for functions,函数直接或间接调用自己的形式被称为递归调用。(1)直接递归:在函数内自调用,例如:int f(int x)int y;z=f(y);/*在f函数内调用自己*/return z;,(2)间接递归:函数之间互调用,例如:void a()b();,void b()a();,递归在解决某些问题中,是一个十分有用的方法,可将复杂问题简单化。因为其一,有的问题它本身就是递归定义的;其二,它可以使某些看起来不易解决的问题变得容易解决和容易描述,使一个蕴含递归关系且结构复杂的程序变得简洁精炼,可读性好。,【例6.13】用递归法计算n!,求n!可用下述公式表示:n!=1(n=0,1)n(n-1)!(n1)分析 求5!5!=4!*54!=3!*43!=2!*32!=1!*2即先求(n-1)!;而求(n-1)!,又需要先求(n-2)!,而求(n-2)!;又可以变成求(n-3)!,如此继续,直到最后变成求1!的问题,而根据公式有1!=l。再反过来依次求出2!,3!,直到最后求出n!。,#includedouble fac(int n)/*若采用int类型,最大只能求18!*/double f;if(n0)printf(n0,input error!);else if(n=0|n=1)f=1;else f=fac(n-1)*n;/*fac函数自调用*/return(f);void main()int n;printf(ninput a inteager number:);scanf(%d,求5!的递归调用及返回过程,main()fac(5)fac(4)fac(3)fac(2)fac(1),【例6.14】,有5个人排成一列,问第5个人多高?他说比第4个人高2公分。问第4个人,他说比第3个人高2公分。问第3个人,又说比第2个人高2公分。问第2个人,说比第1个人高2公分。最后问第1个人,他说是170公分。请问第5个人多高?,分析 显然,这是一个递归问题。要求第5个人的身高,就必须先知道第4个人的身高,而第4个人的身高也不知道,要求第4个人的身高必须先知道第3个人的身高,而第3个人的身高又取决于第2个人的身高,第2个人的身高取决于第1个人的身高。而且每一个人的身高都比其前一个人高2公分。递归出口:high(n)=170(n=1)递归式子:high(n-1)+2(n1),#includeint high(int n)int h;if(n=1)h=170;else h=high(n-1)+2;return(h);void main()printf(high=%dn,high(5);,high函数递归调用过程:,main()high(5)high(4)high(3)high(2)high(1),6.4 标识符的作用域与存储方式,C语言程序由函数组成,每个函数都要用到多个标识符,如变量名、常量名、函数名等。需完成的任务越复杂,组成程序的函数就越多,涉及到的各种标识符名也越多。一般情况下,为了便于多人合作编程,我们希望保证函数的独立性。第一,各函数中标识符可以任意命名,只要符合标识符的命名规定即可;第二,由于变量是用于保存数据的存储单元,每个函数中的数据都要有自己的存储单元。在实际应用中有时候希望各函数间有较多的数据联系,甚至组成程序的各文件之间共享某些数据。因此,为了既能保证数据的独立性,又能实现数据共享的目的,C语言采用全局变量和局部变量来解决上述问题。,6.4.1 变量的作用域,变量的生命期被称为变量的作用域。前面讨论函数的形参时已知,函数的形参仅在该函数被调用时,才分配内存单元,函数执行结束后立即释放。这一点表明形参变量的生命期仅在所定义的函数内有效,离开该函数就不能再使用了。C语言中所有的变量都有自己的作用域。变量声明的方式不同,位置不同,其作用域也不同。C语中的变量,按作用域范围可分为两种,即局部变量(local variable)和全局变量(global variable)。,1.局部变量,1)局部变量的声明及其作用域 在函数内声明的变量,被称为局部变量。局部变量的作用域仅限于所声明的函数内,在该函数内才能引用,并随该函数结束而消亡。,float f1(int a)/*函数f1*/int b,c;a、b、c有效char f2(int x,int y)/*函数f2*/int i,j;x、y、i、j有效main()/*主函数*/int m,n;m、n有效,【例6.16】在f函数中引用main函数中的变量,则是错误的。,#includevoid f()int t=2;/*t的作用域为f 函数内*/a*=t;b/=t;/*错,引用main函数中的变量是非法*/void main()int a,b;scanf(“%d,%d”,2)复合语句中声明的局部变量,除了作用于函数的局部变量外,C语言还允许在复合语句中声明局部变量,其有效使用范围当然也被局限于所声明的复合语句内。,#includevoid main()int a,b;/*a和b的作用域为main()函数*/a=1;b=5;int b=2;/*b的作用域为复合语句内*/b=a+b;a=a+b;printf(a=%d b=%dn,a,b);,【例6.17】,2.全局变量,1)全局变量的声明及其作用域全局变量是指声明在函数外而不属于任何函数的变量。全局变量的作用域是从其声明的位置开始到程序所在文件的结束,它对作用范围内所有的函数都起作用,可解决多个函数间的变量共用问题。全局变量的声明格式与局部变量完全一致,只是声明的位置不同而已,全局变量的声明既可放在程序的开头,也可以放在两个函数的中间,只要在函数外部即可。一般情况下把全局变量的声明放在程序的最前面,即第一个函数的前面。,【例6.18】全局变量的声明。,#includeint x;/*x为全局变量*/void main()int a;a=1;x=a;/*在main函数中对全局变量x赋值*/int b=2;b=a+b;x=x+b;/*在复合语句中使用全局变量x*/printf(a=%d b=%dn,a,x);,【例6.19】求n个班同学英语四级的平均分。,#includeint n=0;/*全局变量n保存总人数,可在每个函数中使用*/float sum(int i);/*全局函数sum提前声明*/void main()int class,i;float s=0,ave;printf(请输入一共有多少班数:);scanf(%d,float sum(int i)/*i为班号*/int n1,j,x;float s=0;/*n1为1个班人数*/printf(请输入%d班的人数:,i);scanf(%d,2)全局变量的屏蔽,由于全局变量和局部变量的作用域不同,因此C语言允许它们同名。在程序中若全局变量与局部变量同名时,系统如何处理呢?在函数内,当全局变量与局部变量同名时,同名的全局变量不起作用,而由局部变量起作用,即全局变量被屏蔽了。对于其他不同名的变量,全局变量仍然有效。同样,对于重名的函数局部变量与复合语句的局部变量,以复合语句中的局部变量为准。,注意,在程序设计中,应尽量使用局部变量,不使用或少使用全局变量。从某个角度看使用似乎受到了限制,但从另一个角度看,它避免了不同函数间的相互干扰,没有副作用。从结构化程序设计角度来说,全局变量增加了程序模块的关联性(耦合性),但影响了程序的独立性(内聚性)。将关联性与独立性比较,显然独立性更重要。综上所述,使用局部变量能充分保证函数的完整性、独立性,使函数成为一个独立的封闭体,除了通过参数传递与函数外部发生联系外,函数与外界是隔绝的。好的程序设计应提高程序模块的内聚性,降低程序模块的耦合性。全局变量只能作为一种特殊手段,在特殊情况下用于多个函数之间的数据交流,一般情况下,应尽量使用局部变量和函数参数。,6.4.2 变量的存储方式,变量还有一个重要的属性,即变量的存储方式。从变量的作用域(即从空间)角度来分,可以分为全局变量和局部变量。从变量值存在的时间(即生存期)角度来分,可以分为静态存储方式和动态存储方式。,1.变量的动态存储方式与静态动态存储方式,变量的静态存储方式,是指在程序运行期间分配固定存储空间的方式。变量的动态存储方式,是在程序运行期间根据需要进行动态的分配存储空间的方式。动态存储和静态存储的管理方式完全不同。动态存储区是使用堆栈来管理的,动态分配与回收存储单元。而静态存储区相对固定,管理较简单,它用于存放全局变量和静态局部变量。为什么要采用两种存储方式呢?由于程序和数据占用的存储单元越多,执行的速度就越慢,尤其是当程序较大时,这种矛盾更加突出。所以将上述两种存储方式配合使用,就可较好的解决这类问题。,用户存储空间可以分为三个部分:,(1)程序区。(2)静态存储区。(3)动态存储区;,计算机内存数据和程序存放图,静态存储区:用来存放全局变量和静态局部变量。在程序开始执行前就给全局变量和静态局部变量分配存储区,在程序执行过程中它们占据固定的存储单元,直到程序执行完毕才释放。动态存储区存放以下数据:(1)函数形式参数;(2)自动变量(局部变量);(3)函数调用时的现场保护和返回地址。对以上这些数据,当函数开始调用时才分配动态存储空间,函数结束时释放这些空间。在C语言中,每个变量和函数有两个属性:数据类型和数据的存储类别。变量声明的完整形式,应如下所示:存储类型符 类型说明符 变量名1,变量名2,;,2.auto变量(自动变量),自动变量的存储类型符为auto,自动变量的存储区被分配在动态存储区中,用来存放局部变量。关键字auto可以省略,auto不写则隐含定为“自动存储类别”,属于动态存储方式。例如:auto int x,y;或 int x,y;函数中的形参和在函数中定义的变量(包括在复合语句中定义的变量),都属此类,在调用该函数时,系统会给它们分配存储空间,在函数调用结束时就自动释放这些存储空间。使用自动变量节省内存,并减少了程序运行时间,所以在程序设计中大多使用自动变量。,3.static变量(静态变量),静态变量的存储类型符为static,静态变量的存储区被分配在静态存储区中,用来存放全局变量和静态局部变量。例如:static int x,y;对于全局变量,由于它和具体函数无关,从程序执行的开始到整个程序的结束,对应的存储单元始终保持。在静态存储区中,除了全局变量外,还有一种特殊的局部变量静态局部变量。静态局部变量兼有全局和局部变量的特点。静态局部变量的声明同静态变量一样,所不同的是声明的位置放在函数内。,静态局部变量有以下特点:,(1)占据固定的存储单元,直到程序结束才释放。(2)静态局部变量的作用域只能在所声明的函数内。(3)如果声明时没有赋初值,系统将自动赋0,并且赋初值只在函数第一次调用时起作用,以后调用都使用前一次调用保留的值。这是因为函数结束后静态局部变量的存储单元并不释放,其上一次的结果仍然被保存,该变量的值尽管不释放,但不能在其他函数中使用。,【例6.21】打印1到5的阶乘值。,#includevoid main()int i,fac(int n);for(i=1;i=5;i+)printf(%d!=%dn,i,fac(i);int fac(int n)static int f=1;/*f=1仅执行1次*/f=f*n;return(f);,【例6.22】若一个系有3个班,求各班同学英语四级的平均分及3个班参考的总人数。,#includeint average(int i);/*全局函数average提前声明*/void main()int i,s=0;for(i=1;i=3;i+)s+=average(i);/*s统计所有班的总人数*/printf(总人数=%d n,s);,int average(int i)/*i为班号*/int n,j,x;float s=0,ave;/*n为1个班人数,sum_n为5个班总人数*/static int sum_n=0;printf(请输入%d班的人数:,i);scanf(%d,4.寄存器变量,寄存器变量是把数据存储在计算机寄存器单元上。寄存器是指CPU(中央处理器)内部的数据存储单元,其数据存取速度比内存单元快得多。从寄存器变量本意上说,其目的是为了加快程序运行速度,但在目前微机上使用的大多数C语言系统,并不真正支持寄存器变量实现,而是把寄存器变量当作普通的自动变量,数据仍然存储在内存单元上。寄存器变量只适用于整形数据。其定义格式如下:register int变量表;例如:register int x,y;,5.外部变量,一个C程序可分成若干个文件保存,每个文件中可保存若干个函数,例如:一位同学负责编写3个函数f1()、f2()及f3(),写好后用文件名file1.c保存,另一个同学负责编写f4()、f5()和main函数,写好后用 文件名file2.c保存,若在这个文件中要用到另一个文件中的变量如何处理呢?用extern来声明外部变量,可扩展外部变量的作用域某个文件中引用另一个文件中的全局变量,只要用extern声明,说明这个变量是在其他文件中已经定义过的外部变量,那么,在该文件中不会为外部变量重新分配内存。,外部变量声明的形式:extern类型说明符 外部变量名;例如:在文件file1.c中定义了全局变量:int students;如果在另一个文件file2.c中的函数f4()中,需要使用students变量,则应作如下处理:f4()extern int students;/*外部变量声明语句*/这样,通过外部变量说明语句,全局变量的作用域可以扩展到文件file2.c的函数f4()中。如果外部变量的声明语句写在文件的头部,就可在该文件的任何函数内使用进行students变量。,【例6.23】,文件f1.c#includefloat PI;void main()float r;PI=3.1415926;scanf(“%f”,文件f2.cextern float PI;float area(float r)return(4.0*PI*r*r);float volume(float r)return(4.0/3.0*PI*r*r*r);,在本例中:(1)extern float PI;放在文件的最前面,故area函数和volume函数均可使用PI的值。(2)由于在文件f2.c中声明了extern float PI;所以在f2.c文件中,系统不会给PI分配存储单元,使用的是f1.c中的PI值。(3)文件f2.c中extern float PI;不能省略,否则系统会给PI分配新存储单元。,【例6.24】用extern声明外部变量,扩展程序中的作用域。,#includeint max(int x,int y)int z;z=xy?x:y;return(z);void main()extern A,B;printf(max=%dn,max(A,B);int A=13,B=-8;,如何运行一个多文件的程序,在编译状态下,建立一个“项目文件”。加入并编辑4个文件,分别以文件名file1.cpp、file2.cpp、file3.cpp、file4.cpp 保存。(3)编译执行。,6.5 编译预处理,在前面各章中,已多次使用过以“#”号开头的预处理命令。如包含命令#include,宏定义命令#define等。在源程序中这些命令都放在函数之外,而且一般都放在源文件的前面,它们称为预处理部分。语言提供了多种预处理功能,如宏定义、文件包含、条件编译等。合理地使用预处理功能编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。,6.5.1 宏定义,宏定义的功能是用一个标识符来表示一个字符串。在编译预处理时,对程序中所有出现的“宏名”,都用宏定义中的字符串去代换,这称为“宏代换”或“宏展开”。在C语言中,“宏”分为有参数和无参数两种。下面分别讨论这两种“宏”的定义和调用。,1.无参宏定义,无参宏定义的一般形式为:,#define 标识符 字符串,其中:#表示这是一条预处理命令;define为宏定义命令;标识符为所定义的宏名,为了避免与变量名混淆,多用大写以示区别;字符串可以是常数、表达式、格式串等。,【例6.25】求n个整数之和。,#include#define N 3void main()int x,j,s=0;for(j=1;j=N;j+)scanf(%d,【例6.26】,#include#define L(x*x+2*x+x)void main()int x,y;printf(input x:);scanf(%d,2.带参数的宏定义,带参宏定义的一般形式为:,带参数宏调用的一般形式为:,例如:#define L(x)(x*x+2*x+x)/*带参数宏定义*/y=L(5);/*宏调用*/在宏调用时,用实参5去代替形参x,经预处理宏展开后的语句为:y=(5*5+2*5+5),【例6.28】,#include#define MAX(a,b)(ab)?a:bvoid main()int x,y,max;printf(input x,y:);scanf(%d%d,【例6.29】带参的宏。,#include#include#define power(y)(y)*(y)void main()int x;double z;printf(input x:);scanf(%d,文件包含,文件包含是C预处理程序的另一个重要功能。文件包含命令行的一般形式为:,文件包含命令的功能:是把指定包含的文件插入该命令行位置取代该命令行,从而把指定包含的文件和当前的源程序文件连成一个源文件。,【例】设文件file1.c中有以下两个函数:,f1()f2()在文件file2.c中将file1.c文件包含进来,就可将3个函数组成一个源文件,再对file2.c进行编译、连接、执行。#include”file1.c”void main(),条件编译,预处理程序提供了条件编译的功能。可以按不同的条件去编译不同的程序部分,即按照条件选择源程序中的不同的语句参加编译,因而产生不同的目标代码文件。这对于程序的移植和调试是很有用的。条件编译有三种形式。,(1)第一种形式,它的功能是:如果标识符已被#deftne命令定义过,则对程序段1进行编译;否则对程序段2进行编译。,【例6.31】条件编译形式一的例程。,#include#define R 1/*若无此命令,则求矩形面积*/void main()float x;double area;printf(请输入圆半径或矩形边长:);scanf(%f,#endif,(2)第二种形式,#ifndef 标识符 程序段1#else程序段2#endif,它的功能是:如果标识符未被#define命令定义过则对程序段1进行编译,否则对程序段2进行编译。这与第一种形式的功能正相反,(3)第三种形式,#if 常量表达式程序段1#else 程序段2#endif,它的功能是:如常量表达式的值为真(非0),则对程序段1 进行编译,否则对程序段2进行编译。因此可以使程序在不同条件下,完成不同的功能。,6.6 函数应用举例,【例6.32】编写求最小公倍数的程序。【例6.34】求s1k+2 k+3 k+nk(由于整数的长度有限,k值不能太大)的值。,