版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第4章函数与编译预处理语言程序设计第四章函数与编译预处理C4.1函数的概念及其分类4.2函数的定义4.3函数的声明与调用4.4变量的作用域和存储类别4.5内部函数与外部函数4.6递归函数4.7编译预处理4.8综合范例4.9本章小结4.10问与答内容简介4.1函数的概念及其分类语言程序设计第四章函数与编译预处理C在第一章中已经介绍过,C源程序是由函数组成的。虽然在前面各章的程序中都只有一个主函数main(),但真正实用的程序往往由大量的小函数而不是由少量大函数构成的,即所谓“小函数构成大程序”。这样的好处是让各部分相对简单独立,并且任务单一。
所谓函数就是一个具有一定功能、且相对独立的、可供其它程序调用的程序模块。它是C源程序的基本模块,通过对函数模块的调用实现特定的功能。C语言中的函数相当于其它高级语言的子程序(或过程)。4.1函数的概念及其分类语言程序设计第四章函数与编译预处理CC语言不仅提供了极为丰富的库函数(如TurboC,MSC都提供了三百多个库函数),还允许用户建立自己定义的函数。用户可把自己的算法编成一个个相对独立的函数模块,然后采取调用的方法来使用函数。可以说C程序的全部工作都是由各式各样的函数完成的,所以也有人把C语言称为函数式语言。由于采用了函数模块式的结构,C语言易于实现结构化程序设计。使程序的层次结构清晰,便于程序的编写、阅读、调试。4.1函数的概念及其分类语言程序设计第四章函数与编译预处理C在C语言中可从不同的角度对函数分类。1.从函数定义的角度看,函数可分为库函数和用户定义函数两种。1)库函数由C系统提供,用户无须定义,只需在程序前用#include宏命令包含要调用的函数原型的头文件,即可在程序中直接调用。在前面各章的例题中反复用到printf
、scanf
、gets、puts等函数均属此类。2)用户自定义函数自定义函数是由程序设计者根据问题的需要自己定义的。
语言程序设计第四章函数与编译预处理C2.C语言的函数兼有其它语言中的函数和过程两种功能,从这个角度看,又可把函数分为有返回值函数和无返回值函数两种。(1)有返回值函数此类函数被调用执行完成之后,将向调用者返回一个执行结果,称为函数返回值。
(2)无返回值函数此类函数用于完成某项特定的处理任务,执行完成之后不向调用者返回函数值。4.1函数的概念及其分类语言程序设计第四章函数与编译预处理C3.从主调函数和被调函数之间数据传送的角度看又可分为无参函数和有参函数两种。(1)无参函数函数定义、函数声明及函数调用中均不带参数。主调函数和被调函数之间不进行参数传送。此类函数通常用来完成一些特定的功能。(2)有参函数也称为带参函数。在函数定义及函数声明时都有参数,定义时指定的参数称为形式参数(简称为形参)。在函数调用时也必须给出参数值或有值的表达式,调用时给出的参数称为实际参数(简称为实参)。进行函数调用时,主调函数将把实参的值传送给形参,供被调函数使用。4.1函数的概念及其分类4.2函数的定义语言程序设计第四章函数与编译预处理C函数定义的一般形式1.现代定义形式:[存储类别][返回值类型]函数名称([类型标识1形参名称1[,类型标识2形参名称2…..]]){[变量定义;…]语句1;……语句n;}
4.2函数的定义语言程序设计第四章函数与编译预处理C函数定义的一般形式2.传统的定义形式[存储类别][返回值类型]函数名([形参名称1[,形参名称2…..]])[类型标识1形参名称1;[类型标识2形参名称2;….]]{[变量定义;…]语句1;……语句n;}
4.2函数的定义语言程序设计第四章函数与编译预处理C【例4-1】编写程序求解任意两个数的最大公约数,并输出结果。int
Maxcd(int
X,intY)/*利用辗转相除法求整数X与Y的最大公约数函数*/{intR;R=X;
if(X<Y){X=Y;Y=R;}
while(R=X%Y){X=Y;Y=R;}returnY;}voidTellMessage(){printf("Inputvaluesintoa&b,please!");}Ex4_1.c演示4.2函数的定义语言程序设计第四章函数与编译预处理Cvoidmain(){int
a,b,R;
TellMessage();/*调用TellMessage()函数,提示输入数据*/
scanf("%d%d",&a,&b);R=Maxcd(a,b);/*调用Maxcd(a,b)函数,求出a和b最大公约数并赋给R*/
printf("%d和%d的最大公约数是%d\n",a,b,R);}
程序说明:以上Maxcd(int
X,intY)函数的功能是专门求解两数的最大公约数。其中的X和Y是形式参数。该函数有返回值(就是X和Y的最大公约数),返回值是整型,因此也被称之为整型函数。TellMessage()函数的功能只是在屏幕上给出提示信息:Inputvaluesintoa&b,please!它既是一个无参的,也是一个无返回值的函数。语言程序设计第四章函数与编译预处理C关于函数定义的几点说明:(1)[存储类别]指明函数的有效范围,此类型标识符只有两种:static和extern。static规定了当前定义的函数,只能被定义它的那个文件中的其它函数调用,不能被其它文件中的函数调用,因此,这类函数又叫作内部函数;extern说明了当前定义的函数可供所有文件中的函数调用,因而又称为外部函数。默认(不明确指定)的是extern,即外部函数。(2)[返回值类型]规定函数有无返回值或规定函数执行结束时,返回值的数据类型,默认的是整型(int)。如果函数不需要或没有返回值,应当明确用void说明。4.2函数的定义语言程序设计第四章函数与编译预处理C(3)函数名称必须是合法的标识符,其与变量命名规则相同。正规的程序设计,变量、函数与符号常量的取名最好要见名知义,这样利于提高程序的可读性和可维护性。良好的命名习惯是优秀程序员的标志之一。(4)[存储类别][返回值类型]函数名称([类型标识1形参名称1[,类型标识2形参名称2…..]])或者[存储类别][返回值类型]函数名([形参名称1[,形参名称2…..]])[类型标识1形参名称1;[类型标识2形参名称2;….]]被称之为函数头,紧接其后的一对花括号”{“和“}”及其中的语句称之为函数体。4.2函数的定义语言程序设计第四章函数与编译预处理C(5)[]中的说明符是可有可无的,例如[形参名称1[,形参名称2…..]]表示函数定义中的形式参数个数≥0。(6)函数体中表示函数结束的三种情况:㈠函数有返回值时,则函数体中表示函数结束的返回语句形式是:return表达式;或return(表达式);㈡函数无返回值时,则函数体中函数结束的返回语句形式是:return;㈢函数无返回值,且函数体中最后一条语句就表示函数的结束,则函数体中无需返回语句return;㈣函数体中可以有多条返回语句。但是,只能有其中的一个返回语句可以被执行。4.2函数的定义语言程序设计第四章函数与编译预处理C(7)有返回值的函数,对它的调用可以出现在表达式中,对无返回值的函数调用只能作为一条单独的语句。(8)函数调用语句中每个实参的数据类型,必须保证与函数定义中的每个形参一一对应相容或相同。(9)函数有返回值时,如果其返回值的类型与定义函数指定的数据类型不一致,则系统会自动转换为定义函数指定的数据类型。10)函数定义不能嵌套,即函数体中不能再包含任何函数的定义。4.2函数的定义语言程序设计第四章函数与编译预处理C【例4-2】编写函数实现从N个整数中找出其中的素数,并且按照从小到大的顺序输出这些素数以及这些素数的平均值(平均值保留两位小数)。#include"stdio.h"#defineNum10#defineTrue1#defineFalse0/*以下是利用选择排序法把数组中N个元素的值从小到大排序的函数*/voidNsort(intA[],intN){intI,J,K,T;for(I=0;I<N-1;I++){K=I;for(J=I+1;J<N;J++)if(A[K]>A[J])K=J;if(I!=K){T=A[I];A[I]=A[K];A[K]=T;}}}4.2函数的定义Ex4_2.c演示语言程序设计第四章函数与编译预处理CvoidAcceptINum(intA[],intN);/*为了使得主函数main能够调用它而对其进行声明*/voidmain(){int
I,IArray[Num],Pn=0;floatfAverage=0;
printf("Input10NumbersintoArray,please!");
AcceptINum(IArray,Num);/*调用AcceptINum函数接收键盘输入Num个数*/Nsort(IArray,Num);/*调用Nsort
函数,对IArray
中的元素值进行排序*/printf("ThesortedNumbersAre:\n");/*从小到大输出各个数*/for(I=0;I<Num;I++)printf(I==Num-1?"%d\n":"%d,",IArray[I]);4.2函数的定义语言程序设计第四章函数与编译预处理Cprintf("\nThePrimesare:");for(I=0;I<Num;I++)/*从小到大输出各个素数及素数平均值*/if(IsPrime(IArray[I])){printf("%d,",IArray[I]);fAverrage+=IArray[I];Pn++;}if(Pn!=0)printf("The
AverrageofPrimesis:%.2f\n",fAverrage/Pn);elseprintf("ThereisnoneofPrimes!\n");}/*以下是接收键盘输入N个整数函数*/voidAcceptINum(intA[],intN){intK;
for(K=0;K<N;K++)
scanf("%d",&A[K]);return;/*因为该函数没有返回值,所以此处的
return可以删去*/}4.2函数的定义语言程序设计第四章函数与编译预处理C/*以下是判断一个整数是否是素数,若是素数,1返回给调用者,否则0返回给调用者*/int
IsPrime(X)intX;{intI,N;
for(I=2;I<=X/2;I++)if(X%I==0)returnFalsereturnTrue;}4.2函数的定义语言程序设计第四章函数与编译预处理C程序说明:以上的例子中①函数Nsort(intA[],intN)没有返回值,因此可以不需要返回语句。②函数IsPrime(intX)虽然只有一个返回值,但可以有两个返回语句。③函数AcceptINum(int
A[],intN)虽然没有返回值,也可以用return语句表示函数结束(当然此处的return语句也可以不需要)。④本例中用到了一维数组(数组的概念以及数组的应用要到下一章才学到),为了便于理解,在此对本例中用到的数组,作简单的说明:这里主函数main中定义的数组IArray[Num],其中的Num是一个符号常量10,即Num与整数10是等价的。元素的引用形式是数组名称[整型表达式],并且整型表达式的值是0~Num-1。因此,可以把数组元素看成是一个普通的变量。
4.2函数的定义语言程序设计第四章函数与编译预处理C一个函数能被另一个函数调用需要以下几个条件:(1)该函数必须已经存在,无论是库函数还是用户自定义函数。(2)调用系统提供的库函数时,与被调用函数相关的头文件,必须在源程序的开头用#include命令把它包含进来。(3)用户自定义函数,在被调用之前,需要对自定义函数进行声明。
4.3函数的声明与调用
4.3.1函数能被使用的条件语言程序设计第四章函数与编译预处理C被调用的用户自定义函数,同时具备下列情况,需要对它进行声明。(1)与调用它的主调函数在同一文件中,(2)它的定义位置在调用它的那个主调函数之后,(3)它的返回值类型不是整型,也不是字符型的;(4)在调用它的那个主调函数之前没有被声明4.3函数的声明与调用
4.3.2函数声明的场合语言程序设计第四章函数与编译预处理C(1)[返回值类型]函数名称([类型标识1形参名称1[,类型标识2形参名称2…..]]);或者:(2)[返回值类型]函数名称([类型标识1[,类型标识2…..]]);最简单的方法就是把被调用函数的函数头,复制到调用它的主调函数之前,然后再加一个分号;就一切搞定了!其实函数声明格式中的形参部分只需给出每个参数的数据类型就可以了,无须给出形参的名称,即使给出形参的名称,其名称可以随意,只要名称符合标识符命名规则即可。4.3函数的声明与调用
4.3.3函数声明的格式语言程序设计第四章函数与编译预处理C(1)在所有函数的最前面;如【例4-3】所示。(2)在所有函数的外部,在调用它的主调函数之前;如【例4-4】所示的f2,f3,f4函数的声明。【例4-4】(3)在调用它的主调函数内部,此时,可以把它看作定义变量那样对待;如【例4-4】所示的f1函数的声明和【例4-5】所示的f1,f2,f3,f4函数的声明
4.3函数的声明与调用
4.3.4函数声明的位置语言程序设计第四章函数与编译预处理C【例4-3】/*在程序的最前面声明*/floatf1(floatx,floaty);floatf2(float,float);floatf3(floata,floatb);floatf4(float,float);main(){floata=78.68,b=36.43;
clrscr();
printf("sum=%f\n",f1(a,b));}floatf1(floatx,floaty){return(f2(x,y)*f3(x,y));}floatf2(floats,floatr){returns+r;}floatf3(floatu,floatv){return(u-v)*f4(u,v);}floatf4(floatu,floatv){returnu/v;}4.3函数的声明与调用Ex4_3.c演示语言程序设计第四章函数与编译预处理C4.3函数的声明与调用【例4-4】main()/*在调用函数的前面声明*/{floatf1(floatx,floaty);floata=78.68,b=36.43;
clrscr();printf("sum=%f\n",f1(a,b));}floatf2(float,float),f3(floata,floatb),f4(float,float);floatf1(floatx,floaty){return(f2(x,y)*f3(x,y));}floatf2(floats,floatr){returns+r;}floatf3(floatu,floatv){return(u-v)*f4(u,v);}floatf4(floatu,floatv){returnu/v;}Ex4_4.c演示语言程序设计第四章函数与编译预处理C【例4-5】main()/*在main函数中的声明*/{floatf1(floatx,floaty),f2(float,float),f3(floata,floatb),f4(float,float);floata=78.68,b=36.43;
clrscr();
printf("sum=%f\n",f1(a,b));}floatf1(floatx,floaty){return(f2(x,y)*f3(x,y));}floatf2(floats,floatr){returns+r;}floatf3(floatu,floatv){return(u-v)*f4(u,v);}floatf4(floatu,floatv){returnu/v;}4.3函数的声明与调用Ex4_5.c演示语言程序设计第四章函数与编译预处理C注意!以下三种情况可以省略对被调函数的声明:(1)如果被调函数的定义出现在主调函数之前,则可以省略声明。如【例4-2】的Nsort函数。(2)如果在所有函数定义之前,在函数的外部已作了函数声明,则在以后的各主调函数中,可以不必对被调函数作声明。如【例4-3】的f1、f2、f3、f4函数。(3)如果函数类型为整型或字符型,则在主调函数中可以不要声明。如【例4-2】中的IsPrime(X)函数。但使用这种方法时,系统无法对参数的类型作检查,因此在调用时若参数使用不当,编译时也不报错。为了程序的安全,建议都加以声明为好。4.3函数的声明与调用
4.3.4函数声明的位置语言程序设计第四章函数与编译预处理C一个函数被定义之后,不被别的函数使用,即使功能再强大,也发挥不了任何作用。通过函数调用语句来达到使用函数的目的。函数调用语句有以下两种形式:(1)无返回值的函数调用语句格式:函数名称([实参列表]);(2)有返回值的函数调用语句格式:变量名=函数名称([实参列表]);此处的变量名的数据类型必须与函数返回值的数据类型相容。无论是上述哪种调用语句,都会去执行被调用函数体中的语句,函数执行完毕,会返回到函数调用语句之后,继续执行调用函数语句之后的其它语句。注意:前面说过函数不可以嵌套定义。但是函数可以嵌套调用。
4.3函数的声明与调用
4.3.5函数的调用、参数与返回值语言程序设计第四章函数与编译预处理C【例4-6】编写程序求5个整数中的最大数,并输出。floatmax(intX,floatY)/*求两数中最大值的函数*/{if(X>=Y)returnX;elsereturnY;}voidmain(){int
a,b,c,d,e,Maxvalue;
printf("Intputvaluesintoa,b,c,d&e,please!\n");
scanf("%d%d%d%d%d",&a,&b,&c,&d,&e);
Maxvalue=max(a,max(max(max(b,c),d),e));/*函数max多重嵌套调用自己*//*也可改成maxvalue=max(max(max(a,b),max(c,d)),e);*/
printf("Maxvalue=%d\n",Maxvalue); }4.3函数的声明与调用
4.3.5函数的调用、参数与返回值Ex4_6.c演示语言程序设计第四章函数与编译预处理C注意!(1)函数返回值的类型取决于函数定义中的函数名称之前的数据类型,与return后表达式值的类型无关。如果return后表达式值的类型与函数的定义中函数名称之前的数据类型不一致,系统会自动转换。例如函数max返回值的类型是实型,函数体中的if(X>=Y)returnX;elsereturnY;语句,不论返回值表达式是X还是Y,它返回值的类型总是实型。(2)函数调用语句中的实参数量与形参数量必须相等,与形参相对应的实参数据类型必须相容。4.3函数的声明与调用
4.3.5函数的调用、参数与返回值语言程序设计第四章函数与编译预处理C函数调用中,如果函数有参数,则系统会按照函数形式参数的个数和对应的数据类型,自动申请内存区域,用于存放函数调用语句中对应的实际参数的值。被调用的函数中形参的值变化与否,都不影响函数调用语句中实际参数的值。即函数的参数值传递是单向的,在函数调用时,系统只是把实际参数的值复制到对应形式参数的内存区域。
4.3函数的声明与调用
4.3.6函数的传值方式语言程序设计第四章函数与编译预处理C【例4-7】voidExchange(float
X,floatY){floatT;T=X;X=Y;Y=T;printf("Innerexchange:X=%3.0f,Y=%3.0f\n",X,Y);}main(){floatX=5,Y=8;printf("Beforeexchange:X=%3.0f,Y=%3.0f\n",X,Y);Exchange(X,Y);printf("Afterexchange:X=%3.0f,Y=%3.0f\n",X,Y);
}4.3函数的声明与调用
4.3.6函数的传值方式Ex4_7.c演示语言程序设计第四章函数与编译预处理C程序说明:【例4-7】中主函数main执行到调用语句Exchange(X,Y);时,此后的程序执行过程如下:1、系统会把此处实参X、Y的值5和8分别赋值给函数Exchange中的形参变量X和Y,2、然后,开始执行Exchange函数,3、在Exchange函数中,通过T=X;X=Y;Y=T;语句序列的执行,实现了Exchange函数中对形参X和Y中值的交换,4、因此,执行到printf("Innerexchange:X=%3.0f,Y=%3.0f\n”,X,Y);语句时,其输出结果是:Innerexchange:X=8,Y=5,说明在Exchange函数体内,确实实现了X与Y的交换。5、Exchange函数执行完成,返回到主函数main中调用Exchange函数语句之后,继续执行下一条语句:printf("Afterexchange:X=%3.0f,Y=%3.0f\n",X,Y);6、该语句输出的结果是:Afterexchange:X=5,Y=84.3函数的声明与调用
4.3.6函数的传值方式语言程序设计第四章函数与编译预处理C局部变量:所谓局部变量,就是在函数或复合语句内定义的变量。函数体内定义的变量,其有效范围只局限于定义它的函数体内。也就是只能在定义它的那个函数体内可以使用它,在函数体外部,它就无效了。同理,复合语句中定义的变量,只能在定义它的那个复合语句中使用它,出了复合语句,就不能引用它了!此外,形参只限于它所在的函数内部使用,因此形参是局部变量。全局变量:所谓全局变量,就是在函数外面定义的变量,因此又称之为外部变量。它的有效范围是从它的定义之点起到定义它的那个文件结束。与它在同一个文件中的所有函数都可以引用它。4.4变量的作用域和存储类别
4.4.1变量的作用域语言程序设计第四章函数与编译预处理C【例4-8】程序及其运行的结果intX=5;/*此处X是全局变量,在它后面的函数都可以引用它*/intAdd(intX,intY){intZ;/*此函数中的X,Y,Z均是局部变量*/Z=X+Y;returnZ
;}intSubX(intY){returnY-X;}/*此函数中的Y是局部变量,X是全局变量*/voidmain(){intX=8,Y=20,Z;/*此函数中的X,Y,Z均是局部变量*/Z=Add(X,Y);
printf(“%d+%d=%d\n”,X,Y,Z);Z=SubX(Y);printf(“%d-5=%d\n”,Y,Z);}
4.4变量的作用域和存储类别Ex4_8.c演示语言程序设计第四章函数与编译预处理C全局变量与局部变量的关系,如同中央政府颁布的国家法令与地方政府颁布的地方法令之间的关系。如果某省政府没有颁布治安处罚条例,那么在该省内处理治安事件的依据,引用的就是国家治安处罚条例;如果该省政府颁布了该省的治安处罚条例,那么在该省内处理治安事件的依据,如果没特别说明,引用的就是指该省的治安处罚条例。就是说局部变量掩盖了同名的全局变量。
4.4变量的作用域和存储类别
4.4.1变量的作用域语言程序设计第四章函数与编译预处理C变量的存储类别是指变量的存储属性,即变量被分配在内存的那种区域内。计算机内存一般被划分成系统区和用户区两种,系统区是指系统程序驻留和工作的区域;用户区就是用户程序驻留和工作的场所。为了便于操作系统为程序分配存储区域和控制用户程序的运行,把程序分成执行命令的操作(代码)和被操作的数据两部分进行存储。其中数据区又被分成静态存储区和动态存储区。因此用于存放用户程序的用户区,被对应地分成代码区和静态存储区和动态存储区。一般来说,内存的动态区用于存放程序的局部变量,静态区用于存放程序的静态局部变量和全局变量。4.4变量的作用域和存储类别
4.4.2变量的存储类别语言程序设计第四章函数与编译预处理C4.4变量的作用域和存储类别
4.4.2变量的存储类别系统区用户区················动态存储区静态存储区数据区程序区代码区语言程序设计第四章函数与编译预处理C变量的存储类别有auto、register、static和exturn四种。auto是局部变量的默认存储类别。register指定为变量分配寄存器,而不是内存区域,就是使用CPU的寄存器存放变量的值。auto、register只能用于定义局部变量。exturn是外部(全局)变量的默认存储类别。static既可以用于定义局部变量,也可以用于定义全局变量。static用于定义局部变量时,出了定义该变量的那个程序块(函数体或复合语句)其变量值仍然保留在内存中;如果被定义的变量未赋初值,系统自动予以置0。static用于定义全局变量时,其变量只能被定义它的那个文件中引用,其它文件不可使用它。此时定义的变量也称之为内部变量。4.4变量的作用域和存储类别
4.4.2变量的存储类别语言程序设计第四章函数与编译预处理C4.4.2.1静态存储的局部变量运用举例【例4-9】分别求出1、2、3、4和5的阶乘并输出。其程序及其运行结果如下:voidfact(intN){staticlongs=1;/*s是静态存储的局部变量*/s=s*N;
printf("%d!=%ld\n",N,s);}voidmain(){intk;
for(k=1;k<=5;k++)fact(k);}4.4变量的作用域和存储类别
4.4.2变量的存储类别Ex4_9.c演示语言程序设计第四章函数与编译预处理C4.4.2.2动态存储的局部变量运用举例【例4-10】把上述程序函数fact()中的staticlongs=1;语句,改成longs=1;则程序及其运行结果如下:voidfact(intN){longs=1;/*s是动态存储的局部变量*/s=s*N;
printf("%d!=%ld\n",N,s);}voidmain(){intk;
for(k=1;k<=5;k++)fact(k);}4.4变量的作用域和存储类别
4.4.2变量的存储类别Ex4_10.c演示语言程序设计第四章函数与编译预处理C在TurboC系统大型程序开发中传统的做法有两种办法:一是在主函数文件(即main函数所在的文件)的首部,利用文件包含宏命令#include指定要包含的其它各个程序文件。然后编译连接主函数文件,生成可执行的程序文件(扩展名为.EXE)。二是编写一个工程文件(其扩展名为.prj),在工程文件中指定组成工程文件的各个程序文件位置与名称,然后编译连接这个工程文件,生成可执行的程序文件(扩展名为.EXE);具体方法请看下面的例子。4.4变量的作用域和存储类别
4.4.2变量的存储类别语言程序设计第四章函数与编译预处理C【例4-11】把计算AN的函数fpow存放在一个文件(如Ex4_11_1.c)中,供其它函数调用。调用它的程序存放在另一个文件(如Ex4_11_2.c)中,其程序如下:/*Ex4_11_1.c文件内容*/#include"stdio.h"externintN;/*声明外部变量N*/externfloatS,A;/*声明外部变量S和A*/voidfpow()/*计算A的N次幂并存放到全局变量S中,即S=AN*/{intk;S=1;for(k=1;k<=N;k++)S=S*A;}4.4变量的作用域和存储类别
4.4.2变量的存储类别Ex4_11_2.c演示语言程序设计第四章函数与编译预处理C4.4变量的作用域和存储类别
4.4.2变量的存储类别/*Ex4_11_2.c文件内容*/#include"d:\Clessons\Example\Ex4_11_1.c
"intN;/*定义外部变量N*/floatA;/*定义外部变量A*/voidmain(){externfloatS;/*声明外部变量S*/printf("InputvaluesintoA&N,please!");
scanf("%f%d",&A,&N); fpow();printf("\nS=%f\n",S); }floatS;/*定义外部变量S*/语言程序设计第四章函数与编译预处理C由以上两个程序文件构成的工程文件(Myproj.prj)如下:/*工程文件Myproj.prj内容*/d:\Clessons\Example\Ex4_11_1.cd:\Clessons\Example\Ex4_11_2.c工程文件Myproj.prj的编译选项中的PrimaryCfile必须指定为main函数所在文件的名称。如图4.6所示,指定为D盘下的Ex4_11_2.c即d:\Clessons\Example\Ex4_11_2.c4.4变量的作用域和存储类别
4.4.2变量的存储类别
语言程序设计第四章函数与编译预处理C4.4.2.4静态外部变量(文件内部变量)的应用举例如果把【例4-11】程序改写成如【例4-12】所示:【例4-12】/*Ex4_12_1.c文件内容*/#include"stdio.h"externintN;/*声明外部变量N*/externfloatS,A;/*声明外部变量S和A*/voidfpow()/*计算A的N次幂并存放到全局变量S中,即S=AN*/{intk;S=1;for(k=1;k<=N;k++)S=S*A;}4.4变量的作用域和存储类别
4.4.2变量的存储类别Myroj1.prj演示语言程序设计第四章函数与编译预处理C4.4变量的作用域和存储类别
4.4.2变量的存储类别/*Ex4_12_2.c文件内容*/staticintN;/*定义内部变量N*/floatS;/*定义外部变量S*/floatA;/*定义外部变量A*/voidmain(){printf("InputvaluesintoA&N,please!");
scanf("%f%d",&A,&N); fpow();printf("\nS=%f\n",S); }当编译该程序文件件Myproj1.prj时,系统会给出如下的错误:LinkerError:Undefinedsymbol'_N'inmoduleEX4_12_1.C!语言程序设计第四章函数与编译预处理C4.4变量的作用域和存储类别4.4.2变量的存储类别但是如果把Ex4_12_2.c文件内容改成如【例4-13】所示【例4-13】利用文件包含的方法实现全局变量的共享。/*Ex4_13_1.c文件内容*/#include"stdio.h"externintN;/*声明外部变量N*/externfloatS,A;/*声明外部变量S和A*/voidfpow()/*计算A的N次幂并存放到全局变量S中,即
S=AN*/{intk;S=1;for(k=1;k<=N;k++)S=S*A;}Ex4_13_2.c演示语言程序设计第四章函数与编译预处理C4.4变量的作用域和存储类别
4.4.2变量的存储类别/*Ex4_13_2.c文件内容*/#include“d:\Clessons\Example\ex4_13_1.c"/*在文件头部把c:\Ex4_13_1.c包含进来*/staticintN;/*定义外部变量N*/floatS;/*定义外部变量S*/floatA;/*定义外部变量A*/voidmain(){printf("InputvaluesintoA&N,please!");
scanf("%f%d",&A,&N); fpow();printf("\nS=%f\n",S); }语言程序设计第四章函数与编译预处理C4.4变量的作用域和存储类别
4.4.2变量的存储类别在文件Ex4_13_2.c的头部,把c:\Ex4_13_1.c包含进来,相当于把c:\Ex4_13_1.c文件的内容放在Ex4_13_2.c文件的前面,即把c:\Ex4_13_1.c文件的内容合并到文件Ex4_13_2.c中,这样,只需对Ex4_13_2.c进行编译连接,就会生成与Ex4_13_2同名的可执行文件Ex4_13_2.exe。既不需要建立工程文件,尽管在Ex4_13_2.c文件中把N定义成静态整型变量,也不会像【例4-12】那样出现错误!这是为什么呢?请同学思考。语言程序设计第四章函数与编译预处理C4.5内部函数与外部函数与指定外部变量的静态存储类别类似,函数也有文件内部函数和可供其它文件调用的外部函数两种类型。如果在一个源文件中定义的函数只能被本文件中的函数调用,而不能被同一源程序其它文件中的函数调用,这种函数称为内部函数。定义内部函数的一般形式是:static类型说明符函数名(形参表)例如:staticint
f(int
a,intb)语言程序设计第四章函数与编译预处理C4.5内部函数与外部函数内部函数也称为静态函数。但此处静态static的含义已不是指存储方式,而是指对函数的调用范围只局限于定义它的文件中。因此在不同的源文件中定义同名的静态函数不会引起混淆。外部函数是指在定义函数时未指定关键字static。这样的函数可供任一C文件中的函数对它调用。定义外部函数的一般形式为:[extern]类型说明符函数名(形参表)例如:externint
f(int
a,intb)如在函数定义中没有指明extern或static则隐含为extern。如果一个文件中要调用外部函数,必须在主调文件中要对被调用的函数进行显式的extern说明。语言程序设计第四章函数与编译预处理C4.5内部函数与外部函数【例4-14】:
1externint
func();2voidmain()3{4func();5}6intmax(inta,intb)7{8return(a>b?a:b);9}prg1.c文件的内容如下:
1externintmax(inta,intb);2staticint
func()3{4intx,y,z;5scanf(“%d%d”,&x,&y);6z=max(x,y);7return(z);8}
prg2.c文件的内容如下:
语言程序设计第四章函数与编译预处理C4.5内部函数与外部函数由于func()被定义成prg2.c文件的内部函数,该函数只能在prg2.c文件的内部使用,不能被其它文件的函数调用,因此prg1.c文件的第1行对函数func()的声明是错误的。而在prg1.c文件中的max(inta,intb)被定义成外部函数,不仅在定义它的文件prg1.c中可以调用它,而且其它文件中也可以调用它。为了使得prg2.c文件的第6行能够调用外部函数max,还必须在第6行之前对它进行声明。prg2.c文件的第1行就是起着这样的作用。语言程序设计第四章函数与编译预处理C4.6递归函数C语言中的函数既可以像4.3.5节所述的那样可以嵌套调用,也可以递归调用。函数体内出现调用自身的语句的函数,称之为递归函数。函数的递归调用是指一个函数在它的函数体内,直接或间接地调用它自身。其间接递归调用如图所示main(){………Func1(x,y);………}Func1(intx,inty){………
Func2(a+2);………}Func2(intw){………Func1(w,u);………}第1步调用
func1第2步调用
func2第3~N步调用func1第N+1步返回到func1第N+2步返回到main语言程序设计第四章函数与编译预处理C4.6递归函数直接递归调用如图所示main(){………Func(x,y);………}Func(intx,inty){………Func(a+2,y-b);………}第2~N步调用自身第1步调用Funct第N+1步返回main语言程序设计第四章函数与编译预处理C4.6递归函数在递归调用中,主调用函数又是被调用函数(如图4.9中的函数Func1调用Func2调用,函数Func2又调用Func1),即函数Func1既是调用者也是被调用者)。执行递归函数将反复调用其自身。每调用一次就进入新的一层。例如有函数f如下:intfun(inta){intb=0,c;b++;c=f(b);returnc;}这个函数是一个递归函数。但是运行该函数将无休止地调用其自身,这当然是不正确的。为了防止递归调用无休止地进行,必须在函数内有终止递归调用的手段。常用的办法是加条件判断,满足某种条件后就不再作递归调用,然后逐层返回。语言程序设计第四章函数与编译预处理C4.6递归函数【例4-15】用递归法计算N!可用下述公式表示
N!=1当N≤1(递归结束条件)N*(N-1)!当N>1为了便于编写程序,我们设计一个变量N的函数F(N)如下:F(N)=1当N≤1(递归结束条件)N*F(N-1)当N>1语言程序设计第四章函数与编译预处理C4.6递归函数【例4-15】用递归法计算N!可用下述公式表示
按上述公式对应的程序如下:longF(intN){if(N<=1)return1;elsereturnN*F(N-1);}main(){intN;longS;printf("\ninputainteagernumber:\n");scanf("%d",&N);if(N<0){printf("Nmustbenotlessthanzero!\n");exit(1);}S=F(N);printf("%d!=%ld",N,S);getch();}
Ex4_15.c演示语言程序设计第四章函数与编译预处理C4.6递归函数程序说明:程序中给出的函数F(intN)是一个递归函数。主函数调用F(N)后即进入函数F(N)执行,如果N=0或N=1时都将结束函数的执行,否则就递归调用函数自身。由于每次递归调用的实参为N-1,即把N-1的值赋予形参N,最后当N-1的值为1时再作递归调用,形参N的值也为1,将使递归终止。然后可逐层退回。设执行本程序时输入为5,即求5!。在主函数中的调用语句即为S=F(N);,进入F函数后,由于N=5,执行N*F(N-1),即5*F(5-1)。该语句对F作递归调用即F(4)。逐次递归展开F(5)=5*F(4)=5*4*F(3)=5*4*3*F(2)=5*4*3*2*F(1)=5*4*3*2*1=120。进行五次递归调用后,F函数形参取得的值变为1,故不再继续递归调用而开始逐层返回主调函数。F(5)的函数调用计算过程如下图所示。图中实线表示调用函数,虚线表示函数结束返回。语言程序设计第四章函数与编译预处理C4.6递归函数voidmain(){…L=factn(5);…}longfactn(5){longL;…L=5*factn(4);return(L);}longfactn(3){longL;…L=3*factn(2);return(L);}longfactn(4){longL;…L=4*factn(3);return(L);}longfactn(1){longL;…return(1);}longfactn(2){longL;…L=2*factn(1);return(L);}函数调用计算过程语言程序设计第四章函数与编译预处理C4.6递归函数一个问题的求解能否设计成递归函数,往往取决于对问题本身描述。如果问题的描述具有如下的三个条件,就可用递归函数求解。1、问题的描述分成两部分,其中前一部分描述问题求解的结束条件,后一部分与原始的描述相似或相同;2、后一部分的描述是原始问题的简化;3、后一部分的描述趋于问题求解的结束。N阶乘问题的求解过程(算法),用自然语言可以描述如下:1、N=0或1时,其阶乘的值是1,2、N>1时,N的阶乘值是N与(N-1)阶乘的积,3、因为N的值经过若干次减1的运算,使得N趋于结束条件0或1。显然,这是对问题的递归描述,且满上述的三个条件,因此可以设计成递归函数来求解。语言程序设计第四章函数与编译预处理C4.6递归函数【例4-16】编写递归函数求解斐波那契数列的第N项的值。斐波那契数列的组成规律是:第一项是0,第二项是1,从第三项起每一项都等于紧邻的前两项之和。即数列的组成如下:0,1,1,2,3,5,8……。根据问题的描述,建立如下的数学模型
Fibo(N)=0N=11N=2Fibo(N-1)+Fibo(N-2)N>2语言程序设计第四章函数与编译预处理C4.6递归函数有了以上数学模型,不难写出如下递归函数:longFibo(intN){if(N==1)return0;
if(N==2)return1;returnFibo(N-1)+Fibo(N-2);}或者定义成:longFibo(intN){return(N==1||N==2)?N-1:Fibo(N-1)+Fibo(N-2);}voidmain(){intN;
printf("InputavalueofN,please!\n");
scanf("%d",&N);if(N>0) printf("F(%d)=%ld\n",N,Fibo(N));
elseprintf("Nmustbegreaterthanzero!\n");}Ex4_16.c演示语言程序设计第四章函数与编译预处理C4.6递归函数【例4-17】求两个整数M和N的最大公约数,按照中国古代的九章算术记载的辗转相除法描述:令R=M%N;当R=0时,则N就是M,N两数的最大公约数(N即为所求),终止计算;否则(即R≠0),求N和R的最大公约数(即求N和M%N的最大公约数)。根据上述,可以建立相应的数学模型:Gcd(M,N)=N当M%N=0Gcd(N,M%N)当M%N≠0语言程序设计第四章函数与编译预处理C4.6递归函数根据上述的数学模型,我们既可以用递归函数实现求解,也可以用迭代的方法编写函数求解。下面我们分写出各自的函数:递归法求解函数:longgcd1(longM,longN){return(M%N==0)?N:gcd1(N,M%N);}循环迭代法求解函数:longgcd2(longM,longN){longR=M%N;while(R){M=N;N=R;R=M%N;}returnN;}语言程序设计第四章函数与编译预处理C4.6递归函数voidmain(){longM,N,t;
printf("InputavalueofN,please!\n");
scanf("%ld%ld",&M,&N);if(M<N){t=M;M=N;N=t;} /*保证M≥N*/printf("GreatestCommondividerof%ld&%ldis%ld!\n",M,N,gcd1(M,N));printf("GreatestCommondividerof%ld&%ldis%ld!\n",M,N,gcd2(M,N));}Ex4_17.c演示语言程序设计第四章函数与编译预处理C4.6递归函数【例4-18】猩猩剥花生问题。动物园管理员第一天给了大猩猩一堆花生。当天这些猩猩拨了三分之二多一只,第二天在剩下的花生中又拨了三分之二多一只。此后每天都拨了前一天剩下的三分之二多一只。到了第10天早上,只剩了一只花生。求动物园管理员给了大猩猩多少只花生?Penut(day)=1day=10Penut(day)=3*(Penut(day+1)+1)0<day<10语言程序设计第四章函数与编译预处理C4.6递归函数根据递推公式Xn-1=3*(Xn+1)编写函数如下:longPenut1(intday){longX=1;
for(;day>1;day--)X=3*(X+1);returnX;}longPenut(intday){if(day==10)return1;return(3*(Penut(day+1)+1));}voidmain(){
printf("The
penutnumber=%ld!\n",Penut(1));printf("The
penutnumber=%ld!\n",Penut1(10));}Ex4_18.c演示语言程序设计第四章函数与编译预处理C4.6递归函数由【例4-17】和【例4-18】我们可以看出,一个问题的求解算法往往有多种。究竟采用什么算法更好,这不是本书讨论的话题。但是有一点,在既可以采用递归也可以采用非递归算法的情况下,尽量不用递归方法。因为递归深度过大,用于存放返回信息的堆栈耗用大量的内存,可能会造成内存不够用的困境。在递归深度不大,采用递归使得问题求解的描述更易理解和实现的情况下,采用递归是一种有效的方法。语言程序设计第四章函数与编译预处理C4.6递归函数【例4-19】编写程序求汉诺塔盘子移动的步骤。本题算法分析如下,设A上有n个盘子。移动的过程可分解为三个步骤:第一步借助于C座,把A上的n-1个圆盘移到B上;第二步把A上的最后一个圆盘移到C上;第三步借助于A座,把B上的n-1个圆盘移到C上;其中第一步和第三步是类同的。当n=3时,第一步和第三步又分解为类同的三步,即把n-1个圆盘从一个座移到另一个座上,这里的n=n-1。显然这是一个递归过程,据此可把算法表述成:Move(N,A,B,C)
=Move(N-1,A,C,B)当N>1Move(N-1,B,A,C)当N>1A→C当N=1语言程序设计第四章函数与编译预处理C4.6递归函数根据上述算法模型可编程如下:#include"stdio.h"voidmove(int
n,char
A,char
B,charC)/*将A座上的n个圆盘移到C座的函数*/{
if(n==1)/*当n=1时,直接将A座上的盘子移到C座上*/printf("%c-->%c\n",A,C);
else{/*当n>1时,借助于C座,将A座上的n-1个盘子移到B座上*/move(n-1,A,C,B);
printf("%c-->%c\n",A,C);/*直接将A座上最后一个盘子,移到C座上*/move(n-1,B,A,C);/*借助于A座,将B座上的n-1个盘子移到C座上*/}}Ex4_19.c演示语言程序设计第四章函数与编译预处理C4.6递归函数main(){intn;printf("\ninputnumber:\n");scanf("%d",&n);printf("thesteptomoving%2ddiskes:\n",n);move(n,'A','B','C');}语言程序设计第四章函数与编译预处理C4.6递归函数程序说明:move函数是一个递归函数,它有四个形参n,A,B,C。n表示圆盘数,A,B,C分别表示三个座。move函数的功能是把A上的n个圆盘移动到C上。当n==1时,直接把A上的圆盘移至C上,输出A→C。如n≠1则分为三步:递归调用move函数,把n-1个圆盘从A移到B;输出A→C;递归调用move函数,把n-1个圆盘从B移到C。在递归调用过程中n=n-1,故n的值逐次递减,最后n=1时,终止递归,逐层返回。语言程序设计第四章函数与编译预处理C4.7编译预处理ANSIC规定可以在C源程序中插入一些传给编译程序的预处理命令,由于这些命令不是C语言的组成部分,不能直接进行编译,通常在编译之前预先进行处理,所以在C语言源程序中,凡是以“#”号开头的一律作为编译预处理命令对待。例如:"#definePAI3.14159",预处理时将程序中所有的PAI置换为指定的字符串3.14159。"#include<math.h>",预处理时就用math.h文件中的实际内容代替该行命令,然后将处理结果和源程序一起进行编译,这就扩充了程序的设计环境,提高了编程效率。语言程序设计第四章函数与编译预处理C4.7编译预处理编译预处理是编译系统的一个组成部分,主要有三类命令:宏定义#define、#undef预处理文件包含#include
条件编译#if、#ifdef、#ifndef、#else、#elif、#endif预处理命令的特点:(1)命令以#开头,表示编译预处理命令行的开始标志;(2)每条命令独占一行,即每一行只能有一条编译预处理命令;(3)行末不能有";"号。语言程序设计第四章函数与编译预处理C4.7编译预处理1.不带参数的宏定义用一个指定的名字代表一个字符串,一般形式为:#define宏名字符串功能:编译预处理时,将程序中所有的该宏名(标识符)用该字符串替换。其中:(1)#define是宏定义命令。(2)宏名是用户定义的标识符,不得与程序中其他标识符同名。宏名中不能含空格,宏名与字符串之间用空格分隔开。字符串两侧没有双引号时,串中不能含空格。(3)如字符串加了双引号,双引号将一起参与替换。(4)预处理时用字符串替换宏名的过程称为宏替换,或宏代换、宏展开。
4.7.1宏定义与宏替换语言程序设计第四章函数与编译预处理C4.7编译预处理【例4-20】通过键盘输入半径的值,求圆的周长、面积和球的表面积、体积。#definePAI3.1415926#defineMESSAGE1"这是一个宏定义示例"#defineMESSAGE2"请输入半径"main(){floatS,R,L,V,T;printf(MESSAGE1);printf("%s",MESSAGE2);scanf("%f",&R);L=2*PAI*R;S=PAI*R*R;T=4*S;V=4/3.0*PAI*R*R*R;printf("L=%10.3f\nS=10.3\nT=%10.3f\nV=%10.3f\n",L,S,T,V);}Ex4_20.c演示语言程序设计第四章函数与编译预处理C4.7编译预处理上述程序经过预处理(即宏替换)之后,源程序的内容如下:main(){floatS,R,L,V,T;printf("这是一个宏定义示例");printf("%s","请输入半径");scanf("%f",&R);L=2*3.1415926*R;S=3.1415926*R*R;T=4*S;V=4/3.0*3.1415926*R*R*R;printf("L=%10.3f\nS=10.3\nT=%10.3f\nV=%10.3f\n",L,S,T,V);}预处理后,宏名PAI、MESSAGE1和MESSAGE2不见了,相应处分别被替换为3.1415926、"这是一个宏定义示例"和"请输入半径"。语言程序设计第四章函数与编译预处理
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 洛阳文化旅游职业学院《体育法》2023-2024学年第一学期期末试卷
- 2024年植保无人机及其配件采购合同
- 单位人员管理制度范例大全
- 地热养殖基地施工合同
- 2024年快手电商合作合同样本版B版
- 商业街区巡逻保安协议
- 大型度假村建设施工管理承包合同
- 临时健身房租赁与教练服务合同
- 2025运输保险合同范本
- 消防栓检查与维护手册
- 读了萧平实导师的《念佛三昧修学次第》才知道原来念佛门中有微妙法
- 周边传动浓缩刮泥机检验报告(ZBG型)(完整版)
- 纸箱理论抗压强度、边压强度、耐破强度的计算
- 土地增值税清算审核指南
- 死亡通知书模板
- 鹬蚌相争课件
- PMC(计划物控)面试经典笔试试卷及答案
- 失业保险金申领表_11979
- 《质量管理体系文件》风险和机遇评估分析表
- 食品安全约谈通知书
- 舒尔特方格A4直接打印版
评论
0/150
提交评论