版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
6.1概述6.2函数的定义6.3函数的调用6.4函数的嵌套及递归调用6.5数组作为函数参数6.6局部变量和全局变量6.7变量的存储类别6.8内部函数和外部函数6.9编译预处理6.10程序举例第6章函数6.1概述6.1.1模块化程序设计思想面对一项复杂任务,通常采取模块化的解决方法。首先,分解该复杂任务成几个大的功能模块,根据需要还可以继续细分,直到分解成一个个功能独立的模块为止。分解的结果可以描述为一棵倒立的大树,如图6.1所示。
图6.1模块化程序设计
在程序设计中,常将一些常用的功能模块编写成函数,放在函数库中供公共选用。要善于利用函数库中的函数,以减少重复编写程序段的工作量。先举一个简单的函数调用的例子。【例6.1】main(){printstar();/*调用printstar函数*/print_message();/*调用print_message*/printstar();/*调用printstar函数*/}printstar()/*printstar函数*{printf("******************\n");}print_message()/*print_message函数*/{printf(“Howdoyoudo!\n”);}运行结果如下:******************Howdoyoudo!******************printstart()和print_message()都是用户定义的函数,分别用来输出一排“*”号和一行信息。说明:(1)一个源程序文件由一个或多个函数组成。一个源程序文件是一编译单位,即以源程序为单位进行编译,而不以函数为单位进行编译。(2)一个C程序由一个或多个源程序文件组成。对较大的程序,一般不希望全放在一个文件中,而将函数和其他内容(如预处理)分别放在若干个源文件中,再由若干源文件组成一个C程序。这样可以分别编写、分别编译,提高调度效率。一个源文件可以为多个C程序公用。(3)C程序的执行从main函数开始,调用其他函数后流程返回到main函数,在main函数中结束整个程序的运行,main函数是系统定义的。(4)所有函数都是平行的,即在定义函数时是互相独立的,一个函数并不从属于另一个函数,即函数不能嵌套定义(这和其他的高级语言可能不同)。函数间可以互相调用,但不能调用main函数。(5)从用户使用的角度看,函数有两种;①标准函数,即库函数。这是由系统提供的,用户不必自己定义这些函数,可以直接使用它们。应该说明,不同的C系统提供的库函数的数量和功能不同,当然有一些基本的函数是相同的。②用户自己定义的函数,用户自己编写的用以解决特定问题。(6)从函数的形式看,函数分两类:①无参函数。如例6.1中printstar()和print_message()就是无参函数。在调用无参函数时,主调函数并不将数据传送给被调用函数,一般用来执行指定的一组操作,printstar()函数的作用是输出18个星号。无参函数可以带回或不带回函数值。②有参函数。在调用函数时,在主调函数和被调用函数之间有数据传递。也就是说,主调函数可以将数据传给被调用函数使用,被调用函数中的数据也可以带回来供主调函数使用。6.1.2C语言程序结构函数是构成C语言程序的基本功能模块,它完成一项相对独立的任务。一个C语言程序是若干函数构成的,在构成C程序的诸多函数中有而且只有一个主函数。函数是程序的最小组成单位。所有函数之间的关系是平行的,没有从属的概念。函数的平行关系使得函数的编写相对独立,便于模块化程序设计的实现。C程序的执行总是从主函数开始,又从主函数结束,其他函数只有通过调用关系发生作用。
6.1.4函数的分类1.从用户角度分从用户角度,函数可分为以下两类。(1)库函数(2)自定义函数①定义函数;②声明函数;③调用函数。具体使用方法将在本章中详细介绍。2.从函数形式分从函数形式角度,函数可分为以下两类。(1)无参函数,函数不带参数。(2)有参函数,函数带有至少一个参数。3.从函函数的返返回值分分从函数是是否有返返回值,,函数可可分为以以下两类类。(1)有有值函数数,调用用该函数数后可以以得到返返回值。。(2)无无值函数数,调用用该函数数后没有有返回值值。无值值函数类类似于其其他高级级语言中中的过程程。6.2函函数数的定义义函数由两两部分构构成:函函数头和和函数体体。函数头给给出函数数相关信信息(类类似“黑黑盒子””中的入入口和出出口),,而函数数体具体体实现函函数的功功能。函数的定定义形式式函数定义义的一般般形式是是:[类型标标识符]函数数名(形形式参数数表列)形式参数数类型说说明{数据描述述部分算法实现现部分}前两行是是函数头头。形式式参数((又简称称形参))表列和和形式参参数类型型说明部部分体现现的是一一个函数数的入口口参数的的个数及及其类型型。类型标识识符说明明了函数数返回值值的类型型,也简简称函数数类型。。函数体用用一对花花括号{}括起来。。函数体体中不仅仅可以使使用数据据描述部部分描述述的变量量,而且且还可以以使用形形式参数数。例6.1函数定义义示例。。floatmax(x,y)floatx,y;{floattemp;if(x>y)temp=x;elsetemp=y;return(temp);}函数的返返回值在函数定定义时需需要描述述函数类类型,但但没有给给出函数数如何得得到返回回值。调调用有值值函数时时,要求求被调函函数返回回数据给给主调函函数,返返回的数数据称为为函数返返回值,,简称函函数值。。得到函函数返回回值的方方法是使使用return语句句。return语语句的功功能有3个。(1)返返回一个个值给主主调函数数。(2)释释放在函函数的执执行过程程中分配配的所有有内存空空间。(3)结结束被调调函数的的运行,,将流程程控制权权交给主主调函数数。return语语句使用用的一般般形式为为:return(表达式式)return语语句应书书写在函函数体的的算法实实现部分分,圆括括号可以以省略。。形参和返返回值的的设定编写函数数时,应应分析该该函数中中哪些量量是函数数的已知知量,那那些是函函数需要要得到的的结果。。设计时时将已知知数据作作为函数数的形参参,已知知数据有有几个,,形参就就有几个个。未知知数据正正是函数数需要得得到的结结果。除除需要分分析已知知和未知知外,还还需要确确定已知知和未知知的数据据类型,,从而完完成对函函数头的的设计。。6.3函函数的调用当函数被调用用时,函数对对应的程序代代码才开始执执行,才能实实现相应的函函数功能。对被调用函数数的声明对被调用函数数的声明有两两种方式:外外部声明和内内部声明。在在主调函数内内对被调函数数所作的声明明称为内部声声明,也称为为局部声明;;在函数外进进行的函数声声明称为外部部声明,如果果声明在程序序最前端,外外部声明又称称为全局声明明。内部声明过的的函数只能在在声明它的主主调函数内调调用。外部声声明过的函数数,从声明处处到本程序文文件结束都可可以被调用。。内部声明应应放在主调函函数的数据描描述部分,外外部声明可以以出现在程序序中任何函数数外。对被调用函数数的声明具体体形式为:函数类型函函数名();例6.5函数声明示例例。main(){intm;floatc;floatsum();/*在主函函数main()内对被被调函数sum()作局局部声明,*/scanf("%d",&m);/*只能在在主函数内调调用声明过的的函数max()*/c=sum(m);printf("c=%f\n",c);}floatsum(intn)/*功功能是计算数数列1/2,2/3,3/5,5/8……的前前n项之和*/{floata,b,t,s;intk;a=1,b=2;s=0.5;for(k=2;k<=n;k++){t=a,a=b,b=a+t;s=s+a/b;}return(s);}函数调用的一一般形式1.函数调用用的一般形式式函数名(实际际参数表列))实际参数表列列是函数入口口参数的实际际值。如例6.5中的csum(m)中的m就是有有确定值的实实际参数,sum(m)是对函数的的调用,调用用结束后得到到返回值赋值值给变量c。。2.形式参数数和实际参数数有参函数在调调用时,主调调函数和被调调函数之间有有数据传递,,主调函数传传递数据给被被调函数。主主调函数传递递来的数据称称为实际参数数,简称实参参。函数定义义时形式参数数仅仅是数据据的抽象代表表,没有具体体值,称为形形参。(1)形式参参数定义函数时,,函数名后的的参数称作形形式参数,简简称形参。在定义函数时时,系统并不不给形参分配配存储单元,,当然形参也也没有具体的的数值,所以以称它是形参参,也叫作虚虚参。形参在函数调调用时,系统统暂时给它分分配存储单元元,以便存储储调用函数时时传来的实参参。一旦函数数结束运行,,系统马上释释放相应的存存储单元。(2)实际参参数在调用函数时时,函数名后后的参数称作作实际参数,,简称实参。。调用函数时,,实参有确定定的值,所以以称它是实际际参数。它可可以是变量、、常量、表达达式等任意““确定的值””。(3)实参和和形参之间的的关系实参的个数、、类型应该和和形参的个数数、类型一致致。调用函数数时,系统给给形参分配存存储单元,并并且把实参的的数值传递给给形参。实参和形参分分别属于主调调函数和被调调函数,具有有不同的内存存单元。所以以,在函数调调用时形参发发生改变,不不会影响到实实参。3.实参和形形参的结合方方式C语言中实参参和形参的结结合采取的是是“单向值传传递”方式,,只有实参传传递参数给形形参,形参不不回传参数给给实参。下面面用例6.7讲述实参和和形参的具体体结合方式。。例6.7实参和形参的的结合方式示示例。main(){floata,b,sum;floatadd();scanf("%f,%f",&a,&b);sum=add(a,b);printf("sum=%f\n",sum);}floatadd(x,y)floatx,y;{floatz;z=x+y;return(z);}程序从主函数数开始执行,,首先输入a,b的数值值(假如输入入3,5),,接下来调用用函数add(a,b)。具体调用用过程如下。。(1)给形参参x,y分配配内存空间。。(2)将实参参b的值传递递给形参y,,a的值传递递给形参x,,于是y的值值为5,x的的值为3。(3)执行函函数体。①给函数体体内的变量分分配存储空间间。即给z分分配存储空间间。②执行算法法实现部分,,得到z的值值为8。③执行return语语句,完成以以下功能。将返回值返回回主调函数,,即将z的值值返回给main()。。释放函数调用用过程中分配配的所有内存存空间,即释释放x,y,,z的内存空空间。结束函数调用用,将流程控控制权交给主主调函数。调用结束后继继续执行main()函函数直至结束束。函数调用前后后实参、形参参的变化情况况如图6.4所示。图6.4例例6.7实实参和形参变变化示意图函数调用的具具体形式有些函数有返返回值,有些些没有返回值值,这两种函函数的调用形形式不同。1.有值函数数的调用形式式(1)函数调调用作为表达达式的一部分分。即函数返返回值参与表表达式的运算算。(2)作为函函数参数。即即函数返回值值又作为另一一个函数的实实参。2.无值函数数的调用形式式无值函数调用用是作为独立立的函数调用用语句出现的的语句,其功功能类似于一一个过程。(1)实参的的类型应和形形参的类型匹匹配。(2)实参和和形参的结合合方向是自右右向左的。(3)实参的的个数和形参参应该一致。。(4)实参可可以是任意能能够代表“确确定的值”的的内容。(5)有值函函数才可以参参于表达式的的运算。6.4函函数的嵌套套及递归调用用函函数的嵌嵌套调用嵌套调用指的的是在函数的的调用过程中中又出现了另另外一种函数数调用,称为为函数的嵌套套调用。例6.8函数嵌套示例例。以下程序的功功能是计算x2sinx在区间[0,,5]的定积积分。程序由由3个函数构构成,分别是是主函数main()、、函数f1()、函数f2()。main()调用函数f2(),在在函数f2()的执行过过程中又调用用了函数f1(),main()嵌套调用了了函数f1()。图6.5例例6.10函数嵌套调调用过程#include"math.h"main(){floatf2();floats=0,h=0.00005,x;for(x=0;x<5;x+=h)s+=f2(x,h);printf("%f\n",s);}floatf2(x,h)floatx,h;{floatf1();return((f1(x)+f1(x+h))*h/2);}floatf1(x)floatx;{return(x*x+sin(x));}函数的递归调调用函数的递归调调用是函数嵌嵌套调用的特特殊形式。一一个函数在它它的函数体内内直接或间接接地调用了自自己的函数称称为函数的递递归调用。例6.9用递归法计算算n!。floatfac(intn){floatf;if(n==0||n==1)f=1;elsef=fac(n-1)*n;return(f);}main()函数如下::main(){intn;floaty;floatfac();printf("inputn:");scanf("%d",&n);y=fac(n);printf("%d!=%.0f\n",n,y);}6.5数数组作函数参参数数组元素作函函数的参数数组元素作函函数的参数与与普通变量作作函数的参数数本质相同。。数组元素作作函数实参时时,仅仅是将将其代表的值值作为实参处处理。数组中元素作作为函数的实实参,与简单单变量作为实实参一样,结结合的方式是是单向的值传传递。例6.10数数组元素作作函数的参数数示例。floatmax(floatx,floaty){if(x>y)returnx;elsereturny;}main(){floatm,a[]={34.2,100,12.3,50,67,65,78,98,89,-20};intk;m=a[0];for(k=1;k<10;k++)m=max(m,a[k]);printf("%.2f\n",m);}程序运行的结结果为:100.00数组名作函数数的参数数组名代表数数组的首地址址,在数组名名作为函数的的参数时,形形参和实参都都应该是数组组名。在函数数调用时,实实参给形参传传递的数据是是实参数组的的首地址,即即实参数组和和形参数组完完全等同,是是存放在同一一存储空间的的同一个数组组,形参数组组和实参数组组共享存储单单元。如果在在函数调用过过程中形参数数组的内容被被修改了,实实际上也是修修改了实参数数组的内容。。例6.11编编写函数实实现数组的逆逆序存放。程序如下。voidchange(x)intx[];{temp=x[0];x[0]=x[1];x[1]=temp;}main(){voidchange();inta[2]={12,24};printf("before:a[0]=%%d,a[1]=%d\n",a[0],a[1]);change(a);printf("after:a[0]=%d,a[1]=%d\n",a[0],a[1]);}程序运行的结结果为:before:a[0]=12,a[1]=24after:a[0]=24,a[1]=12显然,用数组组名作函数的的参数,才能能真正实现两两个数据的交交换。因为,,数组名作函函数的形参和和实参时,调调用函数把实实参数组的首首地址传递给给形参数组,,这样两个数数组共享存储储单元,在函函数调用时对对形参数组元元素值的交换换,实质上也也是对实参数数组元素值的的交换。用数组名作为为函数参数应应注意以下几几点。(1)数组名名作函数参数数时,可省略略数组的长度度。(2)形参数数组可以和实实参数组同名名。(3)实参数数组应足够大大,即实参数数组提供的内内存空间应大大于或等于形形参数组需要要的内存空间间。(4)数组名名作函数参数数时,应将数数组的长度也也作为函数的的参数,这样样编写的函数数具备通用性性。例6.12编编写函函数用“起起泡法”排排序。“起泡法””的基本思思想是对N个数构成成的序列两两两比较求求出最大值值。假设有有5个数8,3,9,4,1,存储到到a数组中中,采用““起泡法””从大到小小排序的过过程如下。。(1)对5个数两两两比较,如如果相临的的两个数不不是从小到到大排列的的,则交换换使之变为为从小到大大排列,比比较结束后后最大值在在序列底部部,于是得得到序列(3,8,4,1,9)。(2)对前前4个数两两两比较得得到序列((3,4,1,8,9)。(3)对前前3个数两两两比较得得到序列((3,1,4,8,9)。(4)对前前2个数两两两比较得得到序列((1,3,4,8,9)。排序工作结结束。从分析编写写程序如下下。#defineN10main(){voidsort();inta[N],i;for(i=0;i<N;i++)scanf("%d",&a[i]);sort(a,N);for(i=0;i<N;i++)printf("%%8d",a[i]);printf("\n");sort(a,5);for(i=0;i<5;i++)printf("%%8d",a[i]);printf("\n");}voidsort(array,n)intarray[],n;{inti,j,t;for(i=n-1;i>0;i--)for(j=0;j<i;j++)if(array[j]>array[j+1]){t=array[j];array[j]=array[j+1];array[j+1]=t;}}运行程序,,输入:123456-123-45612358853410110↙则输出:-456-123110236.6变变量的作作用域局部变量在一个函数数内定义的的变量称为为局部变量量。局部变变量的作用用范围是定定义它的函函数。关于局部变变量的作用用域需要说说明以下几几点。(1)主函数中中定义的变变量也只能能在主函数数中使用,,不能在其其他函数中中使用。(2)形参参属于被调调函数的局局部变量,,实参属于于主调函数数的局部变变量。(3)允许许在不同的的函数中使使用相同的的变量名,,它们代表表不同的对对象,分配配不同的单单元,互不不干扰。(4)在复合合语句中也也可定义变变量,其作作用域只在在本复合语语句范围内内。全局变量函数外定义义的变量称称作全局变变量。全局局变量可以以被定义它它的文件中中的所有函函数使用。。全局变量量的作用范范围是从定定义变量的的位置开始始到它所在在源文件的的结束。6.7变变量的存存储方式C语言中的变变量不仅有有类型属性性,而且还还有存储类类别的属性性。完整的变量量定义应该该确定它的的两种属性性:存储类类型和数据据类型。变量定义的的完整形式式为:[存储类型型]类类型说明符符变量量名表列;;C语言中,,变量有4种存储类类型,分别别为自动类类型、静态态类型、外外部类型和和寄存器类类型。变量在计算算机内存的的存储情况况分为静态态存储和动动态存储两两种。自动存储类类型关键字auto表示示变量是自自动存储类类型。自动存储类类型的变量量具有动态态性。自动动存储类型型变量的作作用范围仅仅局限于定定义它的函函数。自动动存储类型型变量的存存储单元分分配在动态态数据区。。寄存器存储储类型关键字register表示示变量是寄寄存器存储储类型。例如,registerinta,b;表示定义变变量a,b是整型并并且是寄存存器存储类类型。寄存器型变变量具有动动态性。寄寄存器存储储类型变量量的作用范范围也是仅仅局限于定定义它的函函数。外部存储类类型关键字extern表示变量量是外部存存储类型。。例如,externdoublex,y;表示定义变变量x,y是双精度度浮点型并并且是外部部存储类型型。外部存储类类型变量具具有静态性性。外部存存储类型变变量定义在在函数外部部,它的作作用域为从从变量的定定义处开始始,到本程程序文件的的末尾。静态存储类类型关键字static表示变量量是静态存存储类型。。例如,staticdoublex,y;表示定义变变量x,y是双精度度浮点型并并且是静态态存储类型型。静态存储类类型变量具具有静态性性。静态存存储类型变变量可以定定义在函数数内部,也也可以定义义在函数外外部。在整整个程序运运行期间,,静态型变变量都占据据存储单元元。6.8内内部函数和和外部函数数同一个源程程序文件中中的函数之之间是可以以互相调用用的,不同同源程序文文件中的函函数之间也也是可以互互相调用的的,根据需需要我们也也可以指定定函数不能能被其他文文件调用。。根据函数数能否被其其他源程序序文件调用用,将函数数分为内部部函数和外外部函数。。6.8.1内部函数数如果一个函函数只能被被本文件中中其他函数数所调用,,它称为内内部函数。。在定义内内部函数时时,在函数数名和函数数类型的前前面加static。即static类型标标识符函函数名((形参表))如staticintfun(inta,intb)内部函数又又称静态函函数。使用用内部函数数,可以使使函数只局局限于所在在文件,如如果在不同同的文件中中有同名的的内部函数数,互不干干扰。这样样不同的人人可以分别别编写不同同的函数,,而不必担担心所用函函数是否会会与其他文文件中函数数同名,通通常把只能能由同一文文件使用的的函数和外外部变量放放在一个文文件中,在在它们前面面都冠以static使之局局部化,其其他文件不不能引用。。6.8.2外部函数数(1)在定定义函数时时,如果在在函数首部部的最左端端冠以关键键字extern,,则表示此此函数是外外部函数,,可供其他他文件调用用。如函数首部部可以写为为externintfun(inta,intb)这样,函数数fun就就可以为为其他文件件调用。C语言规定定,如果在在定义函数数时省略extern,则隐隐含为外部部函数。本本书前面所所用的函数数都是外部部函数。(2)在需需要调用此此函数的文文件中,用用extern声明明所用的函函数是外部部函数。【例6.14】有一个字符符串,内有有若干个字字符,今输输入一个字字符,要求求程序将字字符串中该该字符删去去。用外部部函数实现现。file1.c(文文件1)main((){externenter_string(charstr[80]);externdelete_string(charstr[],charch);externprint_string(charstr[]);/*以上3行声明在在本函数中中将要调用用的在其他他文件中定定义的3个个函数*//charc;charstr[80];;enter_strng(str);;scanf("%c",&c);delete_string(str,c);;print_string(str);}file2.c(文文件2)#include<stdio.h>enter_string(charstr[80])/*定义外外部函数enter_srting*/{gets(str);}/*向向字符数组组输入字符符串*/file3.c(文文件3)delete_string(charstr[],charch)/*定定义外部部函数delete_string*/{inti,j;for(i=j=0;;str[i]!="\0";i++)if(str[i]!=ch)str[j++]=str[i];str[j]="\0";}file4.c(文件件4)print_string(charstr[])/*定义义外部函函数print_string*/{printf("%s",str);}运行情况况如下::abcdefgc↙(输输入str)c↙(输输入要删删去的字字符)abdefg((输输出已删删去指定定字符的的字符串串)整个程序序由4个个文件组组成。每每个文件件包含一一个函数数。主函函数是主主控函数数,除声声明部分分外,由由4个函函数调用用语句组组成。其其中scanf是库函函数,另另外3个个是用户户自己定定义的函函数。函函数delete_string的的作用是是根据给给定的字字符串和和要删除除的字符符ch,,对字符符串作删删除处理理。算法法是这样样的:对对str数组的的字符逐逐个检查查,如果果不是被被删除的的字符就就将它存存放在数数组中,,见图6.21(设删删除空格格)。从str[0]开始逐逐个检查查数组元元素值是是否等于于指定要要删除的的字符,,若不是就就留在数数组中,,若是就就不保留留。从图图中可以以看到,,应该使使str[0]赋给str[0],,str[1]str[1],str[2]str[2],str[3]str[3],,然后,,str[5]str[4],………请读者者注意分分析如何何控制i和j的的变化,,以便使使被删除除的字符符不保留留在原数数组中。。这个题题目当然然可以设设两个数数组,把把不删除除的字符符――赋赋给新数数组。但但我们只只用一个个数组,,只把不不被删除除的字符符保留下下来。由由于I总总是二于于或等于于j,因因此最后后保留下下来的字字符不会会覆盖未未被检测测处理的的字符。。最后将将结束符符"\o"也复复制到被被保留的的字符后后面。程序中3个函数数都定义义为外部部函数。。在main函函数中用用extern声明在在main函数数中用到到的enter_string、delete_string、print_string是在在其他文文件中定定义的外外部函数数。通过此例例可知::使用extern声声明就能能够在一一个文件件中调用用其他文文件中定定义的函函数,或或者说把把该函数数的作用用域扩展展到本文文件。Extern声声明的形形式就是是在函数数原型基基础上加加关键字字extern(见本本例main函函数中的的声明形形式)。。由于函函数在本本质上是是外部的的,在程程序中经经常要调调用外部部函数,耿方便便编程,,C语言言允许在在声明函函数时省省写extern。例例8.21程序序main函数数中对power函数数的声明明就没有有用extern,但但作用相相同,一一般都省省写extern,例例如例8.22程序main函数中中的第一一个函数数声明可可写成enter_string(charstr[80])这就是我我们多次次用过的的函数原原型。由由此可以以进一步步理解函函数原型型的作用用。用函函数原型型也能够够把函数数的作用用域扩展展到定义义该函数数的文件件之外((不必使使用extern)。。只要在在使用该该函数的的每一个个文件中中包含该该函数的的函数原原型即可可。函数数原型通通知编译译系统::该函数数在本文文件中稍稍后定义义,或在在另一文文件中定定义。利用函数数原型扩扩展函数数作用域域最常见见的例子子是#include命令的的应用。。在前面面几章中中曾多次次使用过过#include命命令,并并提到过过:#include命令所所指定的的“头文文件”中中包含有有调用库库函数时时所需的的信息。。例如,,在程序序中需要要调用sin函函数,但但三角函函数并不不是由用用户在本本文件中中定义的的,而是是存放在在数学函函数库中中的。按按以上的的介绍,,必须在在本文件件中写出出sin函数的的原型,,否则无无法调用用sin函数。。Sin函数的的原型是是doublesin(doublex)显然,要要求程序序设计者者在调用用库函数数时先从从手册可可查出所所用的库库函数的的原型,,并在程程序中一一一写出出来是麻麻烦而困困难的。。为减少少程序设设计者的的困难,,在头文文件math.h中包包括了所所有数学学函数的的原型和和其他有有关信息息,用户户只需用用以下#include命令令:#include<math.h>>这样,在在该文件件中就能能合法地地调用各各数学库库函数了了。6.9编编译译预处理理ANSIC标准规规定可以以在C源源程序中中加入一一些“预预处理命命令”((preprocessordirectives)),以改改进程序序设计环环境,提提高编程程效率。。这些预预处理命命令是由由ANSIC统一规规定的,,但是它它不是C语言本本身的组组成部分分,不能能直接对对它们进进行编译译(因为为编译程程序不能能识别它它们。))必须在在对程序序进行通通常的编编译(包包括词法法和语法法分析、、代码生生成、优优化等))之前,,先对程程序中这这些特殊殊的命令令进行““预处理理”,即即根据预预处理命命令对程程序作相相应的处处理(例例如,若若程序中中用#define命命令定义义了一个个符号常常量A,,则在预预处理时时将程序序中所有有的A都都置换为为指定的的字符串串。若程程序中用用#include命命令包含含一个文文件“stdio.h”,则则在预处处理时将将stdio.h文件件中的实实际内容容代替该该命令))。经过预处处理后程程序不再再包括预预处理命命令了,,最后再再由编译译程序对对预处理理后的源源程序进进行通常常的编译译处理,,得到可可供执行行的目标标代码。。现在使使用的许许多C编编译系统统都包括括了预处处理、编编译和连连接等部部分,在在进行编编译时一一气呵成成。因此此不少用用户误认认为预处处理命令令是C语语言的一一部分,,甚至以以为它们们是C语语句,这这是不对对的。必必须正确确区别预预处理命命令和C语句、、区别预预处理和和编译,,才能正正确使用用预处理理命令。。C语言言与其他他高级语语言的一一个重要要区别是是可以使使用预处处理命令令和具有有预处理理的功能能。C提供的的预处理理功能主主要有以以下三种种:1.宏定定义2.文件件包含3.条件件编译分别用宏宏定义命命令、文文件包含含命令、、条件编编译命令令来实现现。为了了与一般般C语句句相区别别,这些些命令以以符号““#”开开头。宏宏定义义1.不带带参数的的宏定义义用一个指指定的标标识符((即名子子)来代代表一个个字符串串,它的的一般形形式为#define标识识符字字符串串这就是已已经介绍绍过的定定义符号号常量。。如:#definePI3.1415926它的作用用是指定定用标识识符PI来代替替“3.1415926”这这个字符符串,在在编译预预处理时时,将程程序中在在该命令令以后出出现的所所有的PI都用用“3.1415926”代代替。这这种方法法使用户户能以一一个简单单的名字字代替一一个长的的字符串串,因此此把这个个标识符符(名字字)称为为“宏名名”,在在预编译译时将宏宏名替换换成字符符串的过过程称为为“宏展展开”。。#define是宏宏定义命命令。【例6.17】】#definePI3.1415926main(){floatl,,s,,r,,v;;printf("inputradius:");scanf("%f",&r);;l=2.0*PI*r;;s=PI*r*r;;v=4.0/3*PI*r*r*r;;printf("i=%10.4f\ns=%10.4f\nv=%10.4f\n",l,s,v);;}运行情情况如如下::inputradius:4l=25.1328s=50.2655v=150.7966说明::(1))宏名名一般般习惯惯用大大写字字母表表示,,以便便与变变量名名相区区别。。但这这并非非规定定,也也可用用小写写字母母。(2))使用用宏名名代替替一个个字符符串,,可以以减少少程序序中重重复写写某些些字符符串的的工作作量。。例如如,如如果不不定义义PI代表表3.1415926则则在程程序中中要多多处出出现3.1415926,不不仅麻麻烦,,而且且容易易写错错(或或敲错错),,用宏宏名代代替,,简单单不易易出错错,因因为记记住一一个宏宏名((它的的名字字往往往用容容易理理解的的单词词表示示)要要比记记住一一个无无规律律的字字符串串容易易,而而且在在读程程序时时能立立即知知道它它的含含义,,当需需要改改变某某一个个常量量时,,可以以只改改变##define命令令行,,一改改全改改。例如,,定义义数组组大小小,可可以用用:#definearray_size1000intarray[array_size];先指定定array_size代表表常量量1000,因因此数数组array大小小为1000,,如果果需要要改变变数组组大小小,只只需改改#define行行:#definearray_size500使用宏宏定义义,可可以提提高程程序的的通用用性。。(3))宏定定义是是用宏宏名代代替一一个字字符串串,也也就是是作简简单的的置换换,不不作正正确性性检查查。如如果写写成#definePI3.l459即把数数字1写成成小写写字母母l,,预处处理时时也照照样代代人,,不管管含义义是否否正确确。也也就是是说预预编译译时不不作任任何语语示检检查。。只有有在编编译已已被宏宏展开开后的的源程程序时时才会会发现现错误误并报报错。。(4))宏定定义不不是C语句句,不不必在在行末末加分分号。。如果果加了了分号号则会会连分分号一一起进进行置置换。。如::#definePI3.1415926;;area=PI*r*r;经过宏宏展开开后,,该语语句为为area=3.1415926*r*r;;显然出出现语语法错错误。。(5))#define命命令出出现在在程序序中函函数的的外面面,宏宏名的的有效效范围围为定定义命命令之之后到到本源源文件件结束束。通通常,,#define命命令写写在文文件开开头,,函数数之前前,作作为文文件一一部分分,在在此文文件范范围内内有效效。(6))可以以用#undef命命令终终止宏宏定义义的作作用域域。例例如::#defineG9.8————main(){|G的有有效范范围┆}#undefG———f1(){┆}由于#undef的的作用,使使G的作用用范围在#undef行处终终止,因此此在f1函函数中,G不再代表表9.8。。这样可以以灵活控制制宏定义的的作用范围围。(7)在进进行宏定义义时,可以以引用已定定义的宏名名,可以层层层置换。。【例6.18】#defineR3.0#definePI3.1415926#defineL2*PI*R#defineSPI*R*Rmain(){printf(”L=%f\nS=%f\n””,L,S);}运行情况如如下:L=18.849556S=28.274333经过宏展开开后,printf函数中的的输出项L被展开为为2*3.1415926*3.0,,S展开为为3.1415926*3.0*3.0,printf函数调用用语句展开开为printf(”L=%f\nS=%f\n””,2*3.1415926*3.0,3.1415926*3.0*3.0);(8)对程程序中用双双撇号括起起来的字符符串内的字字符,即使使与宏名相相同,也不不进行置换换。如例9.2中的的printf函数数内有两个个L字符,,一个在双双撇号内,,它不被宏宏置换,另另一个在双双撇号外,,被宏置换换展开。(9)宏定定义是专门门用于预处处理命令的的一个专用用名词,它它与定义变变量的含义义不同,只只作字符替替换,不分分配内存空空间。2.带参数数的宏定义义不是进行简简单的字符符串替换,,还要进行行参数替换换。其定义义的一般形形式为#define宏名名(参数表表)字符串串字符串中包包含在括弧弧中所指定定的参数。。如:#defineS(a,b)a*barea=S(3,2);定义矩形面面积S,a是b是边边长。在程程序中用了了S(3,,2),把把3、2分分别代替宏宏定义中的的形式参数数a、b,,即用3*2代替S(3,2),因此此赋值语句句展开为area=S(3,2);对带参的宏宏定义是这这样展开置置换的:在在程序中如如果有带实实参的宏((如S(3,2))),则按#define命令令行中指定定的字符串串从左到右右进行置换换。如果串串中包含宏宏中的形参参(如a、、b),则则将程序语语句中相应应的实参((可以是常常量、变量量或表达式式)代替形形参。如果果宏定义中中的字符串串中的字符符不是参数数字符(如如a*b中中的*号)),则保留留。这样就就形成了置置换的字符符串,见图图6.22。【例6.19】#definePI3.1415926#defineS(r)PI*r*rmain(){floata,area;a=3.6;area=S(a);printf(”r=%f\narea=%f\n”,a,area);}运行结果如如下:r=3.600000area=40.715038赋值语句““area=S(a)”;经经宏展开后后为area=3.1415926*a*a;说明:(1)对带带参数的宏宏的展开只只是将语句句中的宏名名后面括号号内的实参参字符串代代替#define命令行中中的形参。。例9.3中语句中中有S(a),在展展开时,找找到#define命令行中中的S(r),将S(a)中中的实参a代替宏定定义中的字字符串“PI*r*r”中的的形参r,,得到PI*a*a。这是容容易理解而而且不会发发生什么问问题。但是是,如果有有以下语句句:area=S(a+b);这时把实参参a+b代代替PI*r*r中中的形参r,成为area=PI*a+b*a+b;请注意在在a+b外面没没有括弧弧,显然然这与程程序设计计得的原原意不符符。原意意希望得得到area=PI*(a+b)*(a+b);为了得到到这个结结果,应应当在定定义时,,在字符符串中的的形式参参数外面面加一个个括弧。。即#defineS(r)PI*(r)*(r)在对S((a+b)进行行宏展开开时,将将a+b代替r,就成成了PI*(a+b)*(a+b)这就达到到了目的的。(2)在在宏定义义时,在在宏名与与带参数数的括弧弧之间不不应加空空格,否否则将空空格以后后的字符符都作为为替代字字符串的的一部分分。例如如,如果果有#defineS(r)PI*r*r被认为S是符号号常量((不带参参的宏名名),它它代表字字符串““(r))PI*r*r””。如果果在语句句中有area=S(a);;则被展开开为area=(r)PI*r*r(a);显然不对对了。有些读者者容易把把带参数数的宏和和函数混混淆。的的确,它它们之间间有一定定类似之之处,在在调用函函数时也也是在函函数名后后的括弧弧内写实实参,也也要求实实参与形形参的数数目相等等。但是是带参的的宏定义义与函数数是不同同的。主要有::(1)函函数调用用时,先先求出实实参表达达式的值值,然后后代入形形参。而而使用带带参的宏宏只是进进行简单单的字符符替换。。例如上上面的S(a+b),,在宏开开展时并并不求a+b的的值,而而只将实实参字符符“a+b”代代替形参参r。(2)函函数调用用是在程程序运行行时处理理的,为为形参分分配临时时的内存存单元。。而宏展展开则是是在编译译前进行行的,在在展开时时并不分分配内存存单元,,不进行行值的传传递处理理,也没没有“返返回值””的概念念。(3)对对函数中中的实参参和形参参都要定定义类型型,二者者的类型型要求一一致,如如不一致致,应进进行类型型转换。。而宏不不存在的的类型问问题,宏宏名无类类型,它它的参数数也无类类型,只只是一个个符号代代表,展展开时代代入指定定的字符符串即可可。宏定定义时,,字符串串可以是是任何类类型的数数据。例如:#defineCHARICHINA(字字符)#definea3.6(数数值)CHARI和a不需要要定义类类型,它它们不是是变量,,在程序序中凡遇遇CHARI均均以CHINA代之;;凡遇a均以3.6代代之,显显然不需需定义类类型。同同样,对对带参的的宏。#defines(r)PI*r*rr也不是是变量,,如果在在语句中中有S((3.6),则则展开后后为PI*3.6*3.6,语句中中并不出出现r。。当然也也不必定定义r的的类型。。(4)调调用函数数只可得得到一个个返回值值,而用用宏可以以设法得得到几个个结果。。【例6.20】】#definePI3.1415926#defineCIRCLE(R,L,S,V)L=2*PI*R;S=PI*R*R;V=4.0/3.0*PI*R*R*Rmain(){floatr,l,s,v;scanf("%f",&r);CIRCLE(r,l,s,v);printf("r=%6.2f,s=%6.2f,v=%6.2f\n",r,l,s,v);}经预编译译宏展开开后的程程序如下下:main(){floatr,l,s,v;scanf("%f",&r);l=2*3.1415926*r;s=3.1415926*r*r;v=4.0/3.0*3.1415926*r*r*r;printf("r=%6.2f,l=%6.2f,s=%6.2f,v=%6.2f\n",r,l,s,v);}运行情况况如下::3.5↙↙r=3.50,1=21.99,s=38.48,v=179.59请注意,,实参r的值已已知,可可以从宏宏带回3个值((l,s,v))。其实实,只不不过是字字符代表表而已,,将字符符r代表表R,l代表L,s代代表S,,v代表表V,而而并未在在宏展开开时求出出l、s、v的的值。(5)使使用宏次次数多时时,宏展展开后源源程序长长,因为为每展开开一次都都使程序序增长,,而函数数调用不不使原程程序变长长。(6)宏宏替换不不占运行行时间,,只占编编译时间间,而函函数调用用则占运运行时间间(分配配单元、、保留现现场、值值传递、、返回))。一般用宏宏来代表表简短的的表达式式比较合合适。有有些问题题,用宏宏和函数数都可以以。如::#defineMAX(x,y)(x)>(y)?(x):(y)main(){inta,b,c,d,t;┆t=MAX(a+b,c+d);┆}赋值语句句展开后后为t=(a+b)>(c+d)?(a+b):(c+d);注意:MAX不不是函数数,这里里只有一一个main函函数,在在main函数数中就能能求出t的值。。这个问题题也可用用函数来来求:intmax(intx,inty){return(x>y?x:y);}main(){inta,b,c,d,t;┆t=max(a+b,x+d);┆}max是是函数,,在main函函数中调调用max函数数才能求求出t的的值。请仔细分分析以上上两种方方法。如果善于于利用宏宏定义,,可以实实现程序序的简化化,如事事先将程程序中的的“输出出格式””定义好好,以减减少在输输出语句句中每次次都要写写出具体体的输出出格式的的麻烦。。【例6.21】】#definePRprintf#defineNL"\n"#defineD"%d"#defineD1DNL#defineD2DDNL#defineD3DDDNL#defineD4DDDDNL#defineS"%s"main(){inta,b,c,d;charstring[]="CHINA";a=1;b=2;c=3;d=4;PR(D1,a);PR(D2,a,b);PR(D3,a,b,c);PR(D4,a,b,c,d);PR(S,string);}运行时输输出以下下结果::1121231234CHINA程序中用用PR代代表printf。以以NL代代表执行行一次““换行””操作。。以D代代表输出出一个整整型数据据的格式式符。以以D1代代表输出出完1个个整数后后换行,,D2代代表输出出2个整整数后换换行,D3代表表输出3个整数数后换行行,D4代表输输出4个个整数后后换行。。以S代代表输出出一个字字符串的的格式符符。可以以看到,,程序中中写输出出语句就就比较简简单了,,只要根根据需要要选择已已定义的的输出格格式即可可。连printf都都可以简简写为PR。写出各种种输入输输出的格格式(例例如实型型、长整整型、十十六进制制整数、、八进制制整数、、字符型型等),,把它们们单独编编成一个个文件,,它相当当一个““格式库库”,用用#include合合令把它它“包括括”到自自己所编编的程序序中,用用户就可可以根据据情况各各取所需需了。显显然在写写大程序序时,这这样做是是很方便便的。““文件件包含””处理所谓“文文件包含含”处理理是指一一个源文文件可以以将另外外一个源源文件的的全部内内容包含含进来。。即将另另外的文文件包含含到本文文件之中中。C语语言提供供了#include命令用用来实现现“文件件包含””的操作作。其一一般形式式为#include”文文件名””或#include<文文件名>图6.23表示示“文件件包含””的含意意。图6.23(a)为文件件file1.c,它它有一个个#include<file2.c>命命令,然然后还有有其他内内容(以以A表示示)。图图6.23(b)为另另一文件件file2.c,文文件内容容以B表表示。经经编译预预处理时时,要对对#include命命令进行行“文件件包含””处理::将file2.c的的全部内内容复制制插入到到#include<file2.c>命命令处,,即file2.c被被包含到到file1.c中,,得到图图6.23(c)所示示的结果果。在编编译中,,将“包包含”以以后的file1.c(即图图6.23(c)所示示)作为为一个源源文件单单位进行行编译。。“文件包包含”命命令是很很有用的的,它可可以节省省程序设设计人员员的重复复劳动。。例如,,某一单单位的人人员往往往使用一一组固定定的符号号常量((如g=9.81,pi=3.1415926,e=2.718,c=………),可可以把这这些宏定定义命令令组成一一个文件件,然后后各人都都可以用用#include命命令将这这些符号号常量包包含到自自己所写写的源文文件中。。这样每每个人就就可以不不必重复复定义这这些符号号常量。。相当于于工业上上的标准准零件,,拿来就就用。【例6.22】】可以将将例6.21程程序改改为::(1))文件件format.h#definePRprintf#defineNL"\n"#defineD"%d"#defineD1DNL#defineD2DDNL#defineD3DDDNL#defineD4DDDDNL#defineS"%s"(2))文件件file1.c#include"format.h"main(){inta,b,c,d;charstring[]="CHINA";a=1;b=2;c=3;d=4;PR(D1,a);PR(D2,a,b);PR(D3,a,b,c);PR(D4,a,b,c,d);PR(S,string);}注意::在编编译时时并不不是作作为两两个文文件进进行连连接的的,而而是作作为一一个源源程序序编译译,得得到一一个目目标((.obj)文文件。。因此此被包包含的的文件件也应应该是是源文文件而而不应应该是是目标标文件件。这种常常用在在文件件头部部的被被包含含的文文件为为“标标题文文件””或““头部部文件件”,,常以以“.h””为后后缀((h为为head(头头)的的缩写写),,如““format.h”文文件。。当然然不用用“.h””为后后缀,,而用用“.c””为后后缀或或者没没有后后缀也也是可可以的的,但但用““.h”作作后缀缀更能能表示示此文文件的的性质质。如果需需要修修改一一些常常数,,不必必修改改每个个程序序,只只需修修改一一个文文件((头部部文件件)即即可。。但是是应当当注意意,被被包含含文件件修改改后,,凡包包含此此文件件的所所有文文件都都要全全部重重新编编译。。头文件件除了了可以以包括括函数数原型型和宏宏定义义外,,也可可以包包括结结构体体类型型定义义(见见第11章章)和和全局局变量量定义义等。。说明::(1))一个个#include命令令只能能指定定一个个被包包含文文件,,如果果要包包含n个文文件,,要用用n个个#include命令令。(2))如果果文件件1包包含文文件2,而而文件件2中中要用用到文文件3的内内容,,则可可在文文件1中用用两个个include命命令分分别包包含文文件2和文文件3,而而且文文件3应出出现在在文件件2之之前,,即在在file1.c中中定义义:#include““file3.h”#include““file2.h”这样,,file1和和file2都都可以以用file3的的内容容。在在file2中中不必必再用用#include<file3.h>了了(以以上是是假设设file2.h在在本程程序中中只被被file1.c包包含,,而不不出现现在其其他场场合))。(3))在一一个被被包含含文件件中又又可以以包含含另一一个被被包含含文件件,即即文件件包含含是可可以嵌嵌套的的。例例如,,上面面的问问题也也可以以这样样处理理,见见图6.24。。它的作作用与与图6.25所所示相相同。。(4)#include命命令中中,文文件名名可以以用双双撇号号或尖尖括号号括起起来,,如可可以在在file1.c中中用#include<flie2.h>或#include““flie2.h”都是合合法的的。二二者的的区别别是用用尖括括弧((即<file2.h>形式式)时时,系系统到到存放放C库库函数数头文文件
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 二零二五年度2025版全面保障二婚离婚财产协议书
- 2024年碳排放交易合同:企业间碳排放权买卖与减排目标
- 2024年环保设备维修工程师聘用合同协议3篇
- 2025年跨境消费贷款合同示范文本3篇
- 2024年葡萄采摘与生态旅游开发合作合同3篇
- 2025年度IDC数据中心智能监控服务协议3篇
- 2025年度新材料研发项目劳动者二零二五版劳动协议合同3篇
- 2024年新型电力设备安装服务协议模板版B版
- 课题申报书:大语言模型辅助的临床生化检验在线教学新模式研究
- 2024年股权融资咨询协议样本版B版
- 工程施工安全交底
- 中班听课记录15篇
- GB/T 8750-2022半导体封装用金基键合丝、带
- 体育科学研究方法学习通课后章节答案期末考试题库2023年
- 2023天津市和平区七年级上学期语文期末试卷及答案
- 校园艺术节比赛评分表
- 挖机租赁协议(通用6篇)
- 院内按病种分值付费(DIP)专题培训
- 有机磷中毒专家共识
- 2023-2024学年辽宁省调兵山市小学数学五年级上册期末高分通关试题
- 地方公务员考试:2022西藏真题及答案
评论
0/150
提交评论