第6章 函数与编译预处理_第1页
第6章 函数与编译预处理_第2页
第6章 函数与编译预处理_第3页
第6章 函数与编译预处理_第4页
第6章 函数与编译预处理_第5页
已阅读5页,还剩103页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第6章函数与编译预处理

张倩第6章函数与编译预处理

函数是C语言程序的基本模块,由于采用了函数模块式的结构,C语言易于实现结构化程序设计,使程序的结构清晰、减少重复编写程序的工作量、提高程序的可读性和可维护性。本章主要介绍函数的定义与调用、函数间的数据传递方法、函数的递归调用、变量的作用域和存储类别以及编译预处理命令等相关内容。

第6章函数与编译预处理

第66.1函数概述6.2函数的调用6.3函数的参数传递方式与函数的返回值6.4函数的嵌套调用与递归调用6.5变量的作用域与存储类别6.6内部函数和外部函数6.7编译预处理6.8文件包含6.9条件编译

6.10程序举例6.1函数概述

6.1.1为什么要用函数问题:如果程序的功能比较多,规模比较大,把所有代码都写在main函数中,就会使主函数变得庞杂、头绪不清,阅读和维护变得困难有时程序中要多次实现某一功能,就需要多次重复编写实现此功能的程序代码,这使程序冗长,不精炼6.1.1为什么要用函数解决的方法:用模块化程序设计的思路采用“组装”的办法简化程序设计的过程事先编好一批实现各种不同功能的函数把它们保存在函数库中,需要时直接用6.1.1为什么要用函数解决的方法:用模块化程序设计的思路函数就是功能每一个函数用来实现一个特定的功能函数的名字应反映其代表的功能6.1.1为什么要用函数在设计一个较大的程序时,往往把它分为若干个程序模块,每一个模块包括一个或多个函数,每个函数实现一个特定的功能C程序可由一个主函数和若干个其他函数构成主函数调用其他函数,其他函数也可以互相调用同一个函数可以被一个或多个函数调用任意多次

6.1.1为什么要用函数mainabcfghdeie6.1.1为什么要用函数可以使用库函数可以使用自己编写的函数在程序设计中要善于利用函数,可以减少重复编写程序段的工作量,同时可以方便地实现模块化的程序设计6.1函数概述

在C语言中,函数是程序的基本组成单位,因此可以很方便地用函数作为程序模块来实现C语言程序。利用函数,不仅可以实现程序的模块化,避免大量的重复工作、简化程序,提高程序的易读性和可维护性,还可以提高效率。例如,以下是使用主函数调用line()函数输出一个简单的信头的程序。

#include<stdio.h>voidline()/*定义函数*/{printf("=======================================\n");}voidmain(){line();/*调用函数*/

printf("%s","XinjiangAgriculturalUniversity\n");printf(“%s”,"No.42Nanchangd,Urumqi,Xinjiang,P.R.China\n");line();/*调用函数*/}程序运行结果:=======================================XinjiangAgriculturalUniversityNo.42NanchangRd,Urumqi,Xinjiang,P.R.China=======================================6.1函数概述说明:

(1)一个C程序由一个或多个程序模块组成,每一个程序模块作为一个源程序文件。对较大的程序,一般不希望把所有内容全放在一个文件中,而是将它们分别放在若干个源文件中,由若干个源程序文件组成一个C程序。这样便于分别编写、分别编译,提高调试效率。一个源程序文件可以为多个C程序共用。说明:

(2)一个源程序文件由一个或多个函数以及其他有关内容(如预处理指令、数据声明与定义等)组成。一个源程序文件是一个编译单位,在程序编译时是以源程序文件为单位进行编译的,而不是以函数为单位进行编译的。说明:

(3)C程序的执行是从main函数开始的,如果在main函数中调用其他函数,在调用后流程返回到main函数,在main函数中结束整个程序的运行。说明:

(4)所有函数都是平行的,即在定义函数时是分别进行的,是互相独立的。一个函数并不从属于另一个函数,即函数不能嵌套定义。函数间可以互相调用,但不能调用main函数。main函数是被操作系统调用的。6.1.2函数的分类

C语言不仅提供了极为丰富的库函数,还允许用户建立自己定义的函数。我们可以从不同的角度对C语言的函数进行分类。6.1函数概述6.1.2函数的分类

1.从函数定义的角度,函数可分为库函数(又称标准函数)和用户自定义函数两种。库函数包括了常用的数学函数、字符和字符串处理函数、输入/输出函数等。对每一类库函数,系统都提供了相应的头文件,该头文件中包含了这一类库函数的声明,如数学函数的说明包含在“math.h”文件中,所以程序中如果要用到库函数时,在程序文件的开头应使用#include命令包含相应的头文件。用户自定义函数是用户根据自己的需要编写的函数,以解决用户的专门需要。6.1函数概述6.1.2函数的分类

2.从函数是否具有返回值的角度,函数分为有返回值函数和无返回值函数两种。有返回值函数被调用执行完后将向调用者返回一个执行结果,即函数返回值。如数学函数sin()等即属于此类函数。无返回值函数用于完成某项特定的处理任务,执行完成后不向调用者返回函数值。由于函数无须返回值,用户在定义此类函数时可指定它的返回值为“空类型”,空类型的说明符为“void”。6.1函数概述6.1.2函数的分类

3.从函数是否带有参数角度,函数分为无参函数和有参函数两种。无参函数在调用时,主调函数并不将数据传送给被调函数。而调用有参数函数时,在主调函数和被调函数之间有数据传送。6.1函数概述6.1.3函数的定义函数是C语言程序的模块结构,除了标准库函数以外,要在C语言程序中使用用户自定义函数,必须遵循函数的先定义、后声明、再使用的步骤,即首先应定义好函数的数据类型、存储类型和函数体,然后才能进行使用。指定函数名字、函数返回值类型、函数实现的功能以及参数的个数与类型,将这些信息通知编译系统。

6.1函数概述指定函数的名字,以便以后按名调用指定函数类型,即函数返回值的类型指定函数参数的名字和类型,以便在调用函数时向它们传递数据指定函数的功能。这是最重要的,这是在函数体中解决的对于库函数,程序设计者只需用#include指令把有关的头文件包含到本文件模块中即可程序设计者需要在程序中自己定义想用的而库函数并没有提供的函数C语言函数的定义形式如下:

[数据类型说明符]函数名([形参定义表]){

说明语句;可执行语句;

}例如:说明:①数据类型确定该函数返回值的数据类型,缺省时系统认为是整型或字符型。②函数名由用户自己确定,必须符合C语言标识符的规则。③形参之间用逗号隔开。函数可以没有形参,但函数名后面的一对圆括号不能缺省。6.1函数概述例6.1编写一个函数,输出由指定数量的指定字符组成的分隔条。#include<stdio.h>#include<string.h>voidline(char

c,intn)/*此函数用于输出由指定字符组成的分隔条*/{inti;

for(i=1;i<=n;i++)/*循环n次*/

putchar(c);/*循环输出n个字符*/

printf("\n");}voidmain(){line('*',47);/*传送'*'字符以及输出的字符个数47*/

printf("%s","XinjiangAgriculturalUniversity\n");

printf("%s","No.42NanchangRd,Urumqi,Xinjiang,P.R.China\n");line('*',47);/*传送'*'字符以及输出的字符个数47*/}程序运行结果:*********************************************

XinjiangAgriculturalUniversityNo.42NanchangRd,Urumqi,Xinjiang,P.R.China*********************************************6.2函数的调用在C语言中,用户可以根据需要调用任何函数来完成某种处理。一个函数调用另一个函数称为函数调用。6.2函数的调用6.2.1函数的调用方式函数调用的一般形式为:函数名(实参表)

实参可以是常量、变量、表达式及函数,各实参之间用逗号隔开,如果函数没有参数,则“实参表”为空。函数的调用有三种方式:

1)函数表达式。函数调用出现在表达式中。例如:

s=area(3,4,5);是一个赋值表达式,把area函数的返回值赋予变量s。

2)函数语句。函数的调用是一个单独的语句。例如:printf("IloveChina.\n");scanf("%d",&a);都是以函数语句的方式调用函数。

3)函数参数。函数的调用出现在参数的位置。例如:printf("%d",min(x,y));max(max(a,b),c);前者把min函数的返回值作为printf函数的实参来使用,而后者把max函数的返回值又作为max函数的实参来使用。在函数调用中还应该注意的一个问题是求值顺序的问题,所谓求值顺序是指对实参表中各量是自左至右使用呢,还是自右至左使用。对此,各系统的规定不一定相同。6.2函数的调用例6.2求m个元素中取出n个元素的组合数。求组合的公式为:这个问题三次计算阶乘,因此编写一个函数计算阶乘,主函数三次调用计算阶乘的函数,即可完成组合的计算。6.2函数的调用voidmain(){longcmn;

intm,n,t;

printf("input

m,n=");

scanf("%d,%d",&m,&n);

if(m<n)/*若m<n,则交换m,n的值*/{t=m;m=n;n=t;}

cmn=fac(m);/*调用fac函数计算m!*/

cmn=cmn/fac(n);/*调用fac函数计算n!,并且计算(m!/n!)*/

cmn=cmn/fac(m-n);/*调用fac函数计算(m-n)!,并且计算m!/(n!(m-n)!)*/

printf("cmn=%ld\n",cmn);/*输出计算结果*/}程序运行结果:inputm,n=10,3↙cmn=1206.2函数的调用#include<stdio.h>longfac(intn)/*此函数用于计算阶乘*/{longt=1;

intk;

for(k=2;k<=n;k++)t=t*k;

return(t);}6.2.2对被调函数原型的说明

C语言程序中一个函数调用另一个函数需要具备的条件是:①被调用的函数必须是已经存在的函数,是库函数或用户自定义函数。②如果调用库函数,必须要在程序文件的开头用#include命令将与被调用函数有关的库函数所在的头文件包含到文件中来。如在前面几章已经用过的文件包含宏命令(详见6.8节内容):

#include<math.h>/*说明被调用函数将要用到数学函数*/6.2函数的调用③如果调用用户自定义函数,并且该函数与调用它的函数(即主调函数)在同一个程序文件中,一般还应该在主调函数中对被调函数作声明。即向编译系统声明将要调用此函数,并将有关信息通知编译。与函数定义的格式对应,函数的声明格式如下:存储类型类型标识符函数名(形参的定义表);例如:要对例6.1和例6.2中被调用函数

voidline(char

c,intn)

作函数原型的声明,只在其最后再加一个分号即可,即:

voidline(char

c,intn);longfac(intn);6.2函数的调用#include<stdio.h>#include<string.h>voidline(char

c,intn);voidmain(){line('*',47);

printf("%s","XinjiangAgriculturalUniversity\n");

printf("%s","No.42NanchangRd,Urumqi,Xinjiang,P.R.China\n");line('*',47);}voidline(char

c,intn){inti;

for(i=1;i<=n;i++)

putchar(c);

printf("\n");}6.2函数的调用6.2.2对被调函数原型的声明以下几种情况可以不在主调函数中对被调函数原型进行声明:①如果被调用函数的定义出现在主调函数之前,可以不必加声明。例如,在例6.1和例6.2中,函数line()和fac()均被写在主函数main()之前,在主函数main()的前面可以不必对被调函数line()、fac()进行声明。如果函数line()和fac()均被写在主函数main()之后,则在主函数main()之前必须对被调函数line()、fac()进行声明。6.2函数的调用②如果一个函数只被另一个函数所调用,在主调函数中声明和在函数外声明是等价的。如果一个函数被多个函数所调用,可以在所有函数的定义之前对被调函数进行声明,这样,在所有主调函数中就不必再对被调函数进行声明了。例如:

6.2函数的调用charletter(char,char);/*以下3行在所有函数之前,且在函数外部*/floatf(float,float);int

i(float,float);voidmain()/*在main函数中要调用letter、f和i函数*/{…../*不必对它所调用的这3个函数进行声明*/}/*下面定义被main函数调用的3个函数*/charletter(charc1,charc2)/*定义letter函数*/{……}floatf(float

x,floaty)/*定义f函数*/{…….}int

i(float

j,floatk)/*定义i函数*/{…….}

由于在文件的开头已对要调用的函数进行了声明,因此编译系统从声明中已经知道了函数的有关情况,所以不必在主调函数中再进行声明。

函数的定义和函数原型的声明不是一回事。函数的定义是对函数功能的确定,包括指定函数名、函数值的类型、形式参数及其类型、函数体等,它是一个完整的、独立的程序函数单位。函数原型的声明的作用是把函数的名字、函数的类型及参数的类型、个数、顺序通知编译系统,以便在调用该函数时系统按此进行对照检查(函数名是否正确,实参和形参的个数、类型、顺序是否一致)。另外,对被调函数原型的声明,仅仅是在已定义的函数的首部最后再加一个分号即可。6.3函数的参数传递方式与函数的返回值6.3函数的参数传递方式与函数的返回值6.3.1函数的参数传递方式函数的参数主要用于在主调函数和被调函数之间进行数据传递。在定义函数时,函数名后面圆括号中的变量名称为形式参数,简称形参。在主调函数中调用一个函数时,函数名后面圆括号中的参数称为实际参数,简称实参。在函数调用时,主调函数把实参的值(实参都必须具有确定的值)传送给被调函数的形参,从而实现主调函数向被调函数的数据传送,达到被调函数从主调函数接收数据的目的。在C语言中,参数的类型不同,其传递方式也不同,下面给出C语言中的参数传递方式。6.3.1函数的参数传递方式

1.简单变量作函数参数简单变量作函数参数时,主调函数把实参的值传送给被调函数的形参,从而实现主调函数向被调函数的数据传送。进行数据传送时,形参和实参具有以下特点:①形参与实参各占独立的存储单元。但是值得注意的是,形参变量只有在被调用时才分配临时内存单元,在调用结束时,立即释放所分配的内存单元。因此,形参只有在函数内部使用,函数调用结束返回主调函数后,则不能再使用该形参变量。②函数调用中发生的数据传送是单向的(也被称为“值传递”方式),即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参,因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。6.3函数的参数传递方式与函数的返回值例6.3编写一个程序,将主函数中的两个变量的值传送给swap函数中的两个形参,交换两个形参的值。#include<stdio.h>voidswap(int

x,inty)/*简单变量作被调函数的形参*/{intt;t=x;x=y;y=t;/*通过中间变量t,进行数据交换*/

printf("x=%d,y=%d\n",x,y);}voidmain(){inta=10,b=20;

swap(a,b);/*调用swap函数时,简单变量a,b做实参*/

printf("a=%d,b=%d\n",a,b);}程序运行结果:x=20,y=10a=10,b=206.3函数的参数传递方式与函数的返回值6.3.1函数的参数传递方式

2.数组作为函数的参数

(1)数组元素作函数的参数数组元素作函数的参数,与简单变量作参数一样,遵循单向的“值传递”。即数组元素把它的值传递到系统为形参变量分配的临时存储单元中。6.3函数的参数传递方式与函数的返回值例6.4一个班学生的成绩已存入一个一维数组中,调用函数统计及格的人数。#include<stdio.h>#defineN10int

fun(intx)/*简单变量作被调函数的形参*/{if(x>=60)return(1);/*若学生成绩及格返回1*/elsereturn(0);/*若学生成绩不及格返回0*/}voidmain(){int

cj[N]={76,80,65,60,58,91,47,63,70,85};

intcount=0,k;

for(k=0;k<N;k++)

if(fun(cj[k]))/*调用fun函数时,数组元素cj[k]做实参*/count++;/*若fun(cj[k])的值为1,则学生及格人数加1*/

printf("count=%d\n",count);/*输出及格的人数*/}程序运行结果:count=8

6.3.1函数的参数传递方式

2.数组作为函数的参数(2)数组名作函数的参数简单变量和数组元素作函数的参数,遵循的是“值传递”方式,而数组名作函数的参数,遵循“地址传递”方式,即在函数调用时,若数组名作函数的参数,则将数组的起始地址(数组名代表数组的起始地址)作为参数传递给形参。换言之,“地址传递”方式的特点是,形参数组和实参数组共同使用同样的内存单元,被调函数中对形参数组的操作其实就是对实参数组的操作,因此,函数中对形参值的改变也会改变实参的值。6.3函数的参数传递方式与函数的返回值例6.5一个班学生的成绩已存入一个一维数组中,调用函数求平均成绩。#include<stdio.h>#defineN10floataverage(float

x[N])/*数组作被调函数的形参*/{floatsum=0,aver;

intk;

for(k=0;k<N;k++)/*计算成绩之和*/sum+=x[k];aver=sum/N;/*计算平均成绩*/

return(aver);}voidmain(){floatcj[N],aver;

intk;

printf("input%dscores:\n",N);

for(k=0;k<N;k++)/*通过键盘输入N个学生的成绩*/

scanf("%f",&cj[k]);aver=average(cj);/*调用average函数时,数组名cj作实参*/

printf("averagescoreis:%6.2f\n",aver);}程序运行结果:input10score:78676058908871546280↙averagescoreis:70.806.3函数的参数传递方式与函数的返回值6.3.1函数的参数传递方式

2.数组作为函数的参数(3)多维数组名作函数的参数多维数组名作函数的参数时,除第一维可以不指定长度外(也可以指定),其余各维都必须指定长度。因此,以下写法都是合法的:

int

fun(inta[3][4])

int

fun(inta[][4])6.3函数的参数传递方式与函数的返回值例6.7使用调用函数的方法,求3×4的矩阵中最大和最小的元素。#include<stdio.h>#defineM3/*定义矩阵的行数*/#defineN4/*定义矩阵的列数*/voidput_matric(int

a[M][N])/*输出矩阵的函数*/{int

i,j;

printf("arrayis:\n");

for(i=0;i<M;i++){for(j=0;j<N;j++)printf("%4d",a[i][j]);

printf("\n");}

printf("\n");}voidmax(int

a[][N],intb[])/*求矩阵元素中最大值和最小值的函数*/{int

i,j;b[0]=a[0][0];/*假设矩阵的第1行第1列的元素是最大的*/b[1]=a[0][0];/*假设矩阵的第1行第1列的元素是最小的*/

for(i=0;i<M;i++){for(j=0;j<N;j++){if(a[i][j]>b[0])/*将矩阵中最大的元素存放在b[0]中*/b[0]=a[i][j];

if(a[i][j]<b[1])/*将矩阵中最小的元素存放在b[1]中*/b[1]=a[i][j];}}}6.3函数的参数传递方式与函数的返回值voidmain(){intb[2],a[M][N]={{2,-1,6,8},{11,45,-25,0},{55,18,3,-7}};

put_matric(a);

max(a,b);

printf("maxvalueis:%4d\nminvalueis:%4d\n",b[0],b[1]);}程序运行结果:arrayis:2-1681145-25055183-7maxvalueis:55minvalueis:-256.3函数的参数传递方式与函数的返回值例6.8使用调用函数,实现矩阵的转置。#include<stdio.h>#defineN3voidput_matric(int

a[N][N])/*输出矩阵的函数*/{int

i,j;

for(i=0;i<N;i++){for(j=0;j<N;j++)printf("%4d",a[i][j]);

printf("\n");}

printf("\n");}6.3函数的参数传递方式与函数的返回值voidfun(int

b[][N])/*实现矩阵转置函数*/{int

i,j,t;

for(i=0;i<N;i++)/*将b矩阵i行j列元素和j行i列元素进行交换*/

for(j=i+1;j<N;j++){t=b[i][j];

b[i][j]=b[j][i];b[j][i]=t;}}voidmain(){inti,j,c[N][N]={{1,55,66},{35,1,75},{25,45,1}};

printf("original

matric:\n");

put_matric(c);/*调用输出矩阵函数,输出原始矩阵*/

fun(c);/*调用矩阵转置函数*/

printf("transposed

matric:\n");

put_matric(c);/*调用输出矩阵函数,输出转置矩阵*/}程序运行结果:

originalmatric:155663517525451transposedmatric:1352555145667516.3函数的参数传递方式与函数的返回值6.3.2函数的返回值函数的值是指函数被调用后,执行函数体中的语句序列后所取得的值。函数的值只能通过return语句返回主调函数。return语句的一般形式为:

return;return表达式;

return(表达式);

return语句的作用是:结束函数的执行,并将表达式的值带回给主调函数。6.3函数的参数传递方式与函数的返回值说明:①表达式的类型应与函数定义的数据类型一致,如果不一致则以函数定义的数据类型为准。②在一个函数中允许有多个return语句,流程执行到其中一个return时,立即返回主调函数。③如果被调函数中没有return语句,函数不是不带回值,而是带回一个不确定的值。为了明确表示函数不带回值,可以定义函数为无类型“void”(或称空类型)。例如:

voidprintline(){printf("**********\n");}6.3函数的参数传递方式与函数的返回值例6.9求一个浮点数的绝对值。#include<stdio.h>floatxabs(floatx)/*此函数用于求一个浮点数的绝对值*/{if(x<0)return(-x);else

return(x);}voidmain(){floatx,y;

printf("inputx=");

scanf("%f",&x);y=xabs(x);/*调用xabs函数,并将其结果赋值给y*/

printf("y=%f\n",y);}程序运行结果:

inputx=-3.5↙y=3.5000006.4函数的嵌套调用与递归调用

6.4.1函数的嵌套调用函数的定义不能嵌套,但函数的调用可以嵌套,即被调用的函数还可以再调用其它函数。函数的嵌套调用执行过程如图6-2所示。其执行过程是:执行main函数中调用f1函数的语句时,即转去执行f1函数,在f1函数中又调用f2函数时,又转去执行f2函数,f2函数执行完毕返回f1函数的断点继续执行,f1函数执行完毕返回main函数的断点继续执行。图6-2函数的嵌套调用执行过程例6.10函数的嵌套调用举例。#include<stdio.h>voidfun2()/*定义函数f2*/{printf("BBB\n");}voidfun1()/*定义函数f1*/{printf("A\n");fun2();/*调用函数f2*/}voidmain()/*主函数*/{fun1();/*调用函数f1*/

printf("CCCCC\n");}程序运行结果:

ABBBCCCCC6.4.2函数的递归调用递归是在连续执行某一处理过程时,该过程中的某一步要用到它自身的上一步(或上几步)的结果。在一个程序中,若存在程序自己调用自己的现象就构成了递归。递归又可分为直接递归和间接递归。如果函数funA在执行过程又调用函数funA自己,则称函数funA为直接递归。如果函数funA在执行过程中先调用函数funB,函数funB在执行过程中又调用函数funA,则称函数funA为间接递归。6.4函数的嵌套调用与递归调用

f2函数调用f1函数函数的递归调用

f函数调用f函数

f1函数调用f2函数直接调用本函数间接调用本函数递归分为两个过程:(1)第一为递推过程:将一个原始问题分解为一个新的问题,而这个新的问题的解决方法仍与原始问题的解决方法相同,逐步从未知向已知推进,最终达到递归结束条件,这时递推过程结束。(2)第二为回归过程:从递归结束条件出发,沿递推的逆过程,逐一求值回归,直至递推的起始处,结束回归过程,完成递归调用。值得注意的是,为了防止递归调用无终止地进行,必须在递归函数的函数体中给出递归终止条件,当条件满足时则结束递归调用,返回上一层,从而逐层返回,直到返回最上一层而结束整个递归调用。6.4函数的嵌套调用与递归调用

例6.11用递归方法求n!。当n>1时,n!=n·(n-1)!,因此求n!的问题转化成了求n·(n-1)!的问题,而求(n-1)!的问题与求n!的解决方法相同,只是求阶乘的对象的值减去1。当n的值递减至1时,n!=1,从而使递归得以结束。如果把求n!写成函数fact(n)的话,则fact(n)的实现依赖于fact(n-1),同理fact(n-1)的实现依赖于fact(n-2),…,最后fact(2)的实现依赖于fact(1),fact(1)=1。由于已知f(1),就可以向回推,计算出f(n)。#include<stdio.h>floatfact(intn){floatt;

if(n==0||n==1)/*当n=0或n=1时,递归结束*/t=1;elset=n*fact(n-1);/*递归调用fact函数,并将值赋给t*/

return(t);/*返回t的值*/}voidmain(){intn;floatf;

printf("inputn=");

scanf("%d",&n);f=fact(n);/*调用fact函数,并将值赋给f*/

printf("%d!=%5.0f\n",n,f);}程序运行结果:

inputn=5↙5!=1206.4函数的嵌套调用与递归调用

fac(5)输出fac(5)mainf=fac(4)×5fac函数n=5f=fac(3)×4fac函数n=4f=fac(1)×2fac函数n=2f=fac(2)×3fac函数n=3f=1fac函数n=1fac(1)=1fac(2)=2fac(3)=6fac(4)=24fac(5)=120120例6.12用递归方法求斐波纳契(Fibonacci)数列的第n项。斐波纳契数列的规律是:每个数等于前两个数之和。即斐波纳契数列的第n项fibona(n)=fibona(n-1)+fibona(n-2),第n-1项fibona(n-1)=fibona(n-2)+fibona(n-3),…,最后fibona(3)=fibona(2)+fibona(1)。即当n=1、n=2时,就可以向回推,计算出fibona(n)。6.4函数的嵌套调用与递归调用

#include<stdio.h>longfibona(intn){longf;

if(n==1||n==2)/*当n=1或n=2时,递归结束*/f=1;

elsef=fibona(n-1)+fibona(n-2);/*递归调用*/

return(f);}voidmain(){longintn;

printf("inputn=");scanf("%ld",&n);printf("fibona=%ld\n",fibona(n));/*调用fibona函数,并输出其值*/}程序运行结果:inputn=20↙fibona=67656.4函数的嵌套调用与递归调用

6.5变量的作用域与存储类别

C语言程序中的任何变量,系统都会在适当的时间为变量分配内存单元,而且每一个变量都有两个属性:数据类型和数据的存储类别。变量的数据类型决定了变量在内存中所占的字节数以及数据的表示方法。变量的存储类别决定了变量的空间上的作用域和时间上的生存期(变量存在的时间)。例如在前面我们已介绍,以某种数据类型声明的函数的形参变量只有在函数被调用期间才分配内存单元,调用结束立即释放。这一点表明形参变量只有在函数内才是有效的,离开该函数就不能再使用了。本节将介绍变量的作用域与存储类别。6.5.1局部变量和全局变量在C语言中,所有的变量都有自己的作用域。变量的作用域是指变量在C程序中的有效范围。变量定义的位置不同,其作用域也不同。C语言中的变量按照作用域,分为局部变量和全局变量,也称为内部变量和外部变量。如果变量定义在某函数或复合语句内部,则称该变量为局部变量。也就是说,局部变量只在定义它的函数内部或复合语句内有效。如果变量定义在所有函数外部,则称该变量为全局变量。也就是说,全局变量的作用范围是从定义变量的位置开始到本程序文件结束,即全局变量可以被在其定义位置之后的其他函数所共享。6.5变量的作用域与存储类别

u、v、w虽然都是全局变量,但它们的作用范围不同。即u、v在整个程序中都可以使用,而w只能在函数f2和主函数main中可以使用,不能在函数f1中使用。另外,允许在不同的函数中使用相同的变量名(如局部变量a、j),它们代表不同的对象,分配不同的存储单元,互不干涉,也不会发生混淆。例6.13下面程序中包含有复合语句,分析其运行结果。

#include<stdio.h>voidmain(){inti=2,j=3,k;k=i+j;{intk=8;/*复合语句开始*/

if(i==2){i=3;printf("k1=%d\n",k);}}/*复合语句结束*/

printf("i=%d\nk2=%d\n",i,k);}程序运行结果:k1=8i=3k2=5例6.14分析下面程序的运行结果。#include<stdio.h>voidfun();intn=5;/*定义全局变量n*/voidmain(){intn=10;/*定义局部变量n*/fun();/*调用函数fun()*/printf("n=%d\n",n);/*局部变量起作用*/}voidfun(){printf("n=%d\n",n);/*全局变量起作用*/}程序运行结果:n=5n=10上述程序中,定义了两个变量n,值为5的n是全局变量,其作用域是整个程序范围,值为10的n是局部变量,其作用域是main函数内,执行主函数时,全局变量被屏蔽不起作用,所以输出10。fun()函数被调用时,该函数中出现的是全局变量,所以输出5。使用全局变量时要注意以下几个问题:①全局变量可以在多个函数中使用,当其中一个函数改变了全局变量的值时,可能会影响其他函数的执行结果。②全局变量在整个程序执行过程中始终占用存储单元,因而浪费内存空间。③在一个函数内定义了一个与全局变量名相同的局部变量(或者是形参)时,局部变量有效,而全局变量在该函数内不起作用。6.5.1局部变量和全局变量

6.5.2变量的动态和静态存储方式编译后的C语言程序,在内存中占用的存储空间通常分为程序区(存放代码)、静态存储区(存放数据)和动态存储区(存放数据)三个部分。其中,程序区中存放的是可执行程序的机器指令;静态存储区中存放的是需要占用固定存储单元的变量;动态存储区中存放的是不需要占用固定存储单元的变量。因而,从存储空间的角度,我们把变量分为动态存储变量和静态变量。6.5变量的作用域与存储类别

动态存储变量(也称自动类变量)是指那些当程序的流程转到该函数时才开辟内存单元,执行后又立即释放的变量。静态存储变量则是指在整个程序运行期间分配固定存储空间的变量。

C语言根据变量的动态和静态存储方式提供了4种存储类别,分别是:auto(动态存储类别)、extern(外部存储类别)、register(寄存器型存储类别)和static(静态存储类别)。一个完整的变量说明的格式如下:存储类别类型变量名表例如:

staticfloatx,y;/*表示定义了静态存储类别的整型变量x和y*/6.5变量的作用域与存储类别

6.5.3局部变量的存储类别局部变量有三种存储类别:auto、static和register型。

1.auto存储类别在函数体内或复合语句内定义变量时,如果没有指定存储类型或使用了auto,系统都认为所定义的变量为auto存储类别。auto存储类别的变量也称为自动变量。例如,以下两种定义方法是等价的。

autointa=2,b;

inta=2,b;

函数中的局部变量,如不专门定义类别,都是动态地分配存储空间的。函数中的形参和在函数中定义的变量,都属于此类,在调用该函数时系统会给它们分配临时的存储空间,在函数调用结束时就自动释放这些存储空间。6.5变量的作用域与存储类别

例6.16分析下面程序的运行结果。#include<stdio.h>voidfun(){intn=2;/*没有指定n的存储类别,因此n为auto存储类别的局部变量*/n++;

printf("n=%d\n",n);}voidmain(){fun();fun();}函数fun()中定义的n为自动变量。第一次调用时,系统为n分配临时存储单元,n的初值为2,执行n++后,n的值为3,输出3。第一次调用后分配给n的存储单元被释放,第二次调用时,系统为n重新分配存储单元,因此输出结果仍然是3。运行结果:n=3n=3

2.static存储类别对于某些局部变量,如果希望在函数调用结束后仍然保留函数中定义的局部变量的值,则可以使用static将该局部变量定义为静态局部变量。例如:

staticdoublex;

静态局部变量是在静态存储区中分配存储空间,在程序的整个执行过程中不释放,因此函数调用结束后,它的值并不消失,其值能够保持连续性。使用静态局部变量时特别要注意初始化,静态局部变量是在编译过程中赋初值的,且只赋一次初值,以后调用函数时不再赋初值,而是保留前一次调用函数时的结果。这一点是局部静态变量和自动变量的本质的区别。6.5变量的作用域与存储类别

例如,如果将第三个语句intn=2;改为staticintn=2;,即将auto存储类别的局部变量改为静态局部变量,则结果不同。因为第一次调用时,系统为n分配临时存储单元,n的初值为2,执行n++后,n的值为3,输出3。第一次调用结束后分配给n的存储单元不释放,第二次调用时,n仍然使用原存储单元,不再执行staticintn=2;语句,n的值不是2,而是3,再执行n++后,n的值为4,因此输出结果是4。6.5变量的作用域与存储类别

#include<stdio.h>voidfun(){intn=2;/*auto存储类别局部变量*/n++;

printf("n=%d\n",n);}voidmain(){fun();fun();}#include<stdio.h>voidfun(){static

intn=2;/*静态局部变量*/n++;

printf("n=%d\n",n);}voidmain(){fun();fun();}运行结果:n=3n=3运行结果:n=3n=4

3.register存储类别自动变量和静态局部变量都存放在计算机的内存中。由于计算机从寄存器中读取数据比从内存中读取速度快,所以为了提高程序的运算速度,C语言允许将一些频繁使用的局部变量定义为寄存器变量,这样程序尽可能地为它分配寄存器存放,而不用内存。例如:

registerintm;6.5变量的作用域与存储类别

使用寄存器变量时要注意以下几点:①CPU中寄存器的数目是有限的,因此只能把极少数的频繁使用的变量定义为寄存器变量。②寄存器变量不是保存在内存中,因此不能进行取地址运算。③只有动态局部变量才能定义成寄存器变量,全局变量和静态局部变量不行。6.5.3局部变量的存储类别6.5.4全局变量的存储类别全部变量有两种存储类别:extern和static型。

1.extern存储类别一个大型的C语言程序可以由多个源程序文件组成,这些源程序文件分别被经过编译和连接之后最终生成一个可执行文件。如果其中一个文件要引用另一个文件中定义的外部变量,就应该在需要引用此变量的文件中用关键字extern把此变量说明为外部的即可。这种说明一般应在文件的开头且位于所有函数的外面。例如:

externintn;

则说明全局变量n在其他源程序文件中已经说明。6.5变量的作用域与存储类别

例6.18用extern将全局变量的作用域扩展到其他源程序文件中举例。文件file1.c的内容如下:

文件file2.c的内容如下:

intn;/*定义全局变量n*/externintn;/*声明全局变量n*/voidmain()voidfun(){n=1;{printf("fun:n=%d\n",n);fun();n++;

printf("main:n=%d\n",n);}}程序运行结果:fun:n=1main:n=2

在file1.c文件中定义了全局变量n,此时给它分配存储单元,在file2.c文件中只是对该变量进行了声明。在main函数中先给n赋值1,后调用fun函数,fun函数先输出n的值,输出1,后执行n++;n=2,返回main函数,再输出2。另外,若在同一个源程序文件中,全局变量的定义位于引用它的函数的后面时,也可以在要引用该全局变量的函数中,用extern来说明该变量是全局的,然后再引用该变量。即全局变量可以被所定义的同一个源程序文件中的各个函数共同引用。6.5变量的作用域与存储类别

例6.19举例说明用extern声明全局变量,以扩展它在源程序文件中的作用域。#include<stdio.h>voidmain(){intm;intmax(intx,inty);

externint

a,b;/*声明全局变量*/m=max(a,b);

printf("max=%d\n",m);}inta=3,b=5;

/*声明全局变量*/intmax(intx,inty){return(x>y?x:y);}程序运行结果:max=56.5变量的作用域与存储类别

6.5.4全局变量的存储类别

2.static存储类别与extern存储类别相反,如果希望一个源程序文件中的全局变量仅限于该文件使用,只要在该全局变量定义时的类型说明前加一个static即可。例如:staticintm;

则说明全局变量m只能在该源程序文件内有效,其他文件不能应用。6.5变量的作用域与存储类别

6.6内部函数和外部函数

一个C语言程序可以由多个函数组成,这些函数既可以在一个文件中,也可以分散在多个不同的文件中,根据函数能否被其它文件中的函数调用,将函数分为内部函数和外部函数。6.6内部函数和外部函数6.6.1内部函数

如果在一个文件中定义的函数,只能被本文件中的函数调用,而不能被其它文件中的函数调用,则称它为内部函数。定义内部函数时在函数类型前面加上关键字static。即:

static类型标识符函数名([形参列表])

{

函数体

}例6.21内部函数的说明示例。文件file1.c的内容如下:文件file2.c的内容如下:

#include<stdio.h>#include<stdio.h>externinta;inta=2;staticvoidfun()voidmain(){{fun();a=a+1;printf("a=%d\n",a);}}在文件file1.c中定义了静态函数fun,这就限制了函数fun的作用域只是在file1.c内,所以file2.c中函数调用语句fun();是错误的,编译时会出错。6.6内部函数和外部函数6.6.2外部函数如果在一个源程序文件中定义的函数,除了可以被本文件中的函数调用外,还可以被其它文件中的函数调用,则称它为外部函数。定义外部函数时在函数类型前面加上关键字extern。即:

[extern]类型标识符函数名([形参列表]){

函数体

}

定义外部函数时,关键字extern可以省略,本书前面定义的函数都是外部函数。在需要调用外部函数的文件中,需要用extern对被调用的外部函数进行如下声明:

extern函数类型函数名([形参列表]);6.6内部函数和外部函数例6.22外部函数的说明示例。文件file1.c的内容如下:文件file2.c的内容如下:

#include<stdio.h>#include<stdio.h>externvoidfun(charstr1[100])voidmain(){{externvoidfun(charstr1[100]);…charstr2[100];}…fun(str2);…}6.6内部函数和外部函数6.7编译预处理

编译预处理是在对源程序进行正式编译之前的处理。即编译预处理负责在正式编译之前对源程序的一些特殊行进行预加工,通过编译系统的预处理程序执行源程序中的预处理。

在C语言程序中加入一些预处理命令,可以改善程序设计的环境,有助于编写、易读、易移植、易调试,也是模块化程序设计的一个工具。6.7编译预处理预处理命令都是以“#”开头,末尾不加分号,以区别C语句与C声明和定义。预处理命令可以出现在程序的任何地方,但一般都将预处理命令放在源程序的首部,其作用域从说明的位置开始到所在源程序的末尾。#include<stdio.h>#definePI3.141492voidmain(){floatr=2;s=PI*r*r;

printf("s=%6.2f\n",s);}#include<stdio.h>voidmain(){#definePI3.141492floatr=2;s=PI*r*r;

printf("s=%6.2f\n",s);}

C语言提供的预处理功能主要有三种:宏定义、文件包含和条件编译,分别用宏定义命令、文件包含命令和条件编译命令来实现。

6.7.1宏定义通过预处理命令#define指定的预处理就是宏定义。宏定义的作用是用标识符来代表一个字符串,一旦对字符串命名,就可在源程序中使用宏定义标识符,系统编译之前会自动将标识符替换成字符串。通常把被定义为宏的标识符称为宏名,在编译预处理时,对程序中所有出现的宏名,都用宏定义中的字符串去替换,这个过程叫宏代换或宏展开。宏定义是由源程序中的宏定义命令完成的,宏替换是由预处理源程序自动完成的。根据是否带参数将宏定义分为不带参数的宏定义和带参数的宏定义。6.7编译预处理

6.7.1宏定义

1.不带参数的宏定义不带参数的宏定义的一般形式如下:

#define标识符字符串其中,define是关键字,它表示宏定义命令。标识符为所定义的宏名,它的写法符合C语言标识符的规则。字符串可以是常数、表达式、格式串等。例如:

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论