资源描述
第五章 函数
第五章 函数
解决大的复杂问题的方法之一是化整为零,分而治之,在程序设计中也采用这样的方法,将大任务分解成若干人的智力能及的小模块,降低程序的复杂性,增加程序的可靠性和可重用性。在C程序中,模块以函数的形式来体现,所以说C程序是由函数构成的语言,是函数式的语言。一个C程序由一个main函数和若干其它函数组成,这些函数可以写在一个文件中,也可以写在若干文件中,无论main函数的位置在何处,程序总是从main函数开始执行,也从main函数结束。图5-1是C程序的组织示意图。
文件1
文件2
文件n
函数1一1
函数2一1
函数n一1
程
序
图5-1 C程序的组织
5.1问题的提出
5.1.1问题
如何使用函数编程计算3!+5!+8!
5.1.2问题分析
题目要求计算三个阶乘的值,相加后,输出结果,核心在于计算阶乘。如果在一个main()函数里完成的程序如下:
例5_1
main()
{ long int j,t=1,s=0;
for(j=1;j<=3;j++) t=t*j;/*计算3阶乘*/
s+=t;
for(t=1,j=1;j<=5;j++) t=t*j;/* 计算5阶乘*/
s+=t;
for(t=1,j=1;j<=8;j++) t=t*j;/* 计算8阶乘*/
s+=t;
printf(“3!+5!+8!=%ld\n”,s);
}
仅仅因为循环次数不同,程序中写了三遍计算阶乘的程序段(三个for语句),程序显得重复累赘,如果把计算阶乘的程序段独立写成一个模块(函数),需要时,调用它,程序的可重用性、可靠性、可维护性都可得到提高,怎样写一个函数呢?,怎样调用这个函数呢?这就是我们学习函数这章的任务。
5.1.3 程序
1.把求阶乘的程序段独立写成一个函数,调用这个函数时,输入n,函数就输出n!,如图5-2。
n!
n
函数
图5-2 求n!函数
例5_2
long jch(int n)
{ long t=1;
int j;
for(j=1;j<=n;j++)
t*=j;
return t;}
2. main函数中三次调用以上函数,并将结果相加后输出。
main()
{long s=0;
s=jch(3)+jch(5)+jch(8);
printf(“3!+5!+8!=%ld\n”,s);}
将以上两个函数输入计算机,运行后输出和例5_1同样的结果: 3!+5!+8!=40446 ,但程序的模块化程度提高,整个程序由两个函数组成,可重用性提高,jch函数可被多次使用,main函数的任务减轻,它主要完成调用其他函数的任务。函数的应用主要涉及三方面的内容:定义、调用、声明。
5.2 函数的定义
函数的定义由函数头和函数体两部分组成,形式如下:
函数头
{
函数体
}
5.2.1函数头
C程序通过函数头来调用函数,因此函数头是函数的用户界面,其形式如下:
类型名 函数名(形参表列)
类型名是函数返回值的类型,如例5_2中的long jch(int n)表示该函数将返回一个长整型数据,无返回值的函数是void型。
函数名是函数的标识,必须是合法的C标识符。
括号中的形参表列是0个或多个以逗号分隔的形式参数,它定义了调用该函数时必须送给该函数的数据类型及数据个数,之所以称为形式参数,是因为函数不被调用时,系统不给这些参数分配存储空间,只在调用发生时,才分配空间,调用结束后,系统又收回空间。这种参数就像剧本中的角色,只有在被演员扮演时才能有演出效果。
5.2.2函数体
函数体是用一对花括号括起来的语句系列(语句块),它描述了函数实现某一功能的执行过程,函数最后要执行返回,返回的作用是:
u 将程序的执行从当前函数返回其上级(调用它的函数)。
u 释放该函数的参数及变量所占用的内存空间。
u 向函数返回一个值(如果函数的类型不是void时)。
当函数要返回一个值时,必须通过return语句返回,其形式如下:
return (表达式);
表达式的类型应该和函数定义时函数名前的类型一致,有冲突时,服从于函数名前的类型,return后的括号可有可无。
例5_3
int absint(int x)/* 返回一个整数的绝对值*/
{
if (x>=0)
return x;
else return -x;
}
C函数只能通过一个return语句返回一个值,所有return语句的返回值类型应与函数名前的类型一致。这个函数虽然有两个return语句,但执行时只可能执行到一个return语句。
函数只完成某一功能,而不返回任何值时,可将函数定义成void类型,在这种函数中return语句可有可无,函数执行完后,有无return语句,都执行返回功能。
例:5_4
void pok()
{printf(“0k”);
return;/* 可以无return*/
}
在一个程序中不能出现同名的多个函数定义,且函数体内不能再定义函数,即不能嵌套定义,如:
int ff1() /* ff1函数定义 */
{
float ff2() /* ff2函数定义出现在ff1函数定义中,错*/
{
}
}
函数的定义就是给出函数的名字,函数的返回值类型,函数的形参名字与类型,函数的实现语句(函数体)。
5.3函数原型、函数声明与函数调用
5.3.1函数原型
调用函数时,系统需要知道下列信息:
u 函数类型
u 函数名
u 函数的参数(个数、类型及顺序)
知道这些信息就可以调用该函数,而不必知道函数的具体实现(函数体),这些信息描述了函数的模型,是函数的用户界面,我们称为函数原型。如:
int absint(int); void pok();
5.3.2函数的声明
在函数调用前,C系统需要知道函数的原型,才能保证函数的正确调用,因此当函数调用在前,定义在后时(当函数为int型时,也可不声明),必须用函数原型声明,以便C系统获取相关信息。声明时参数名可以缺省,但类型名不可以缺省,如:
int ff1(int age,char sex); 也可以写为:int ff1(int ,char);
声明中的参数名相当于注释,以便系统检查调用时实参的位置,所以可以缺省。
5.3.3函数的调用
函数的定义相当于写剧本,函数的调用相当于演戏,函数的定义只是说明了函数的存在,函数必须通过调用才能被执行,函数调用的功能是:
u 实参数向形参数传递数据。(由演员担当角色)
u 为参数和函数体内的变量分配内存空间。
u 中断当前函数的执行,把执行流程转向被调用函数的入口,执行被调用函数。
函数调用通过调用表达式进行,其形式为:
函数名(实参表列)
当函数无返回值时,调用表达式后加分号,作为调用语句使用,如:pok();当函数有返回值时,调用表达式作为其他表达式的一部分,如:c=max(a,b); 函数调用结束后,返回到调用时的中断处,但形参的值不传递给实参,参数的传递是单向值传递,见下例。
例:5_5调用mult10时:
实参变量n的值
新分配的形参单元 n
例5_5:将一实数乘以10后显示出来
5.76
main()
{float n=5.76,result;
float mult10(float); /* 函数的声明*/
result=mult10(n); /* 函数的调用*/
printf(“result=%f\n ”,result);
printf(“num=%f\n ”,n);
}
float mult10(float n) /* 函数的定义*/
{ n*=10;
return(n);
}
程序运行结果:
程序运行结果:
result=57.600002 图5-3 单向值传递
num=5.760000
变量作函数的形式参数时,实参可以是表达式(包括已有确定值的变量,常量),当执行到调用表达式时,计算表达式的值,将实参的值传递给形参。函数返回时,形参的值不传给实参,这被称作单向值传递。形参和实参是不同的变量,即便它们有相同的名字,也互不相干,如本例 形实参都是n,但形参n的值不影响实参n。
说明:
程序从main函数开始执行,执行到函数调用mult10(n)时,将实参n变量中的值传递给形参的n变量,执行mult10函数中的语句,形参n变量乘10后,返回到main函数,并将mult10函数的结果57.600002(形参n变量的值)赋给result变量,形参n变量和实参n变量是不同的内存单元,mult10函数调用结束后,形参n变量的值不传给实参n变量,因此实参n变量的值不会改变。调用的时候,实参变量中的值传递给形参变量,调用结束后,形参变量的值不传给实参变量,这就是单向值传递,见图5-3。
例5_6: 求s=s1+s2+s3+s4的值。其中:s1=1+1/2+1/3+…+1/50;s2=1+1/2+1/3+…+1/100;
s3=1+1/2+1/3+…+1/150;s4=1+1/2+1/3+…+1/200
float fc(int n) /*定义函数求1+1/2+1/3+…+1/n的值*/
{float s;
int i;
s=0;
for(i=1; i<=n; i++)
s+=1/(float)i;
return(s);}
main() /*主函数*/
{float sum;
sum=fc(50)+fc(100)+fc(150)+fc(200);/* 四次调用fc()函数,使该函数的形参n分别 为n=50;n=100;n=150;n=200;*/
printf(“sum=%f ”,sum);
}
程序的运行结果:
sum=21.155796
5.4数组名作函数的参数
5.4.1问题的提出
在程序设计中,我们经常需要对一组数据排序,如对若干同学的成绩按由高到低的顺序排序,对若干选手的成绩排序,对工厂每月的产品产量排序等,所以有必要写一个能对若干数据排序的函数,需要时调用它即可完成排序任务,这个函数应该怎样定义呢?
5.4.2问题分析
对大量同类型的数据排序,这些数据应怎样存放是第一个要考虑的问题,根据我们已有的知识,这些数据只能存放在一维数组中,因此送给排序函数进行排序的对象是个一维数组,这是排序函数的第一个参数,其次由于每次需要排序的数目不同,因此还必须有第二个参数,告知该函数需要排序的数据个数,这个参数肯定是个整型变量,这样我们就有下面该函数的轮廓:
void sort(一维数组,int n)
{
用某种算法对该数组排序
}
现在的问题是在C语言中,用一维数组作参数时,应怎样表达?根据前面的知识,实参和形参的类型应该一致,因此,我们的第一个参数,只能是float,int,char这些基本类型中的一个数组,C语言允许一维数组作形参时,可不定义大小,因此如果对整型数据排序,这个函数头如下:
void sort(int s[],int n)
{
用某种算法对该数组排序
}
在函数体中我们只要用某种算法排序s数组即可。
5.4.3程序
例5_7 用选择法排序的该函数如下:
void sort(int s[],int n)
{
int j,t,k;
for(j=0;j<n-1;j++)
for( k=j+1;k<n;k++)
if(s[j]<s[k])
{t=s[j];s[j]=s[k];s[k]=t;}
}
main()
{ int j,a[]={60,70,55,89,90,100,67,88,76,95};
sort(a,10);
for(j=0;j<10;j++)
printf(“%d,”,a[j]);
}
程序运行结果:
100,95,90,89,88,76,70,67,60,55,
函数必须通过调用才能被执行,因此,必须在main()函数中定义实参数组,并调用该函数,C语言规定,形参是数组时,实参应是同类型的数组名,如本例中的a,数组名是数组的首地址,将实参数组的首地址传给形参数组,这样实参数组和形参数组共用同一段内存单元,对形参数组的操作就是对实参数组的操作,因此形参数组排序完成,实参数组也完成了排序,如图5-3所示:
a[0]
a[9]
60
70
55
89
90
100
67
88
76
95
s[9]
s[0]
图5-3
现在您可以写一个函数求一维数组的平均值,并调用它吗?请完成。
例5_8 编写求字符串长度的函数。
strlen(char s[])
{int i;
for(i=0;s[i]!=’\0’;i++);
return(i);
}
请编写main函数,调用该函数,测试某字符串的长度。
5.5函数的嵌套调用和递归调用
C语言不可以嵌套定义,但可以嵌套调用,在调用一个函数时,这个函数又调用其他函数就称函数嵌套调用。嵌套调用也遵循前面介绍的调用规则,即从哪里调用该函数,该函数执行完成后也返回到哪里。
例5_9
int f1(int a,int b)/*定义f1函数*/
{int c;
a+=a;b+=b;
c=f11(a,b); /*调用f11函数*/
return c*c;
}
int f11(int a,int b) /*定义f11函数*/
{ int c;
c=a*b%3;
return c;
}
main()
{ int x=11,y=19;
printf(“%d\n”,f1(x,y)); /*调用f1函数*/
}
程序的执行结果是:4
程序的执行过程如下图:
main()
{
… f1(x,y)
}
{ a=22;b=38;
c=f11(a,b);
return c*c
{ c=22*38%3
return c
}
①
main()
f1(int a,int b)
f11(int a,iny b)
③
④
2
②
42
图5-4
在调用函数的时候,这个函数又调用自己就叫递归调用,递归调用是嵌套调用的特例,是程序设计中常用的方法之一。
例5_10 求n!的方法可以用递归的方法表达,即5!=4!*5,而4!=3!*4,3!=2!*3,2!=1!*2,1!=1,递归公式为:
1 (n=0,1)/* 递归的终点 */
n!=
n(n-1)! (n>1) /* 递归调用 */
根据公式我们就可以写出求n!的函数。
float ff(int n)
{ float f;
if(n<0) printf(“n<0 data errar !”);
else if(n==0||n==1) f=1; /*递归的终点*/
else f=ff(n-1)*n; /*递归调用*/
return f;
}
main()
{ int n;
float y;
printf(“input a integer number:”);
scanf(“%d”,&n);
y=ff(n);
printf(“%d!=%f\n”,n,y);
}
递归调用一定要有结束调用的条件,本例中当n=0或1时,f=1,结束递归调用,当n>1时,发生递归调用,即在ff函数中又调用ff函数(ff(n-1)),下面以5!为例,分析该函数的执行过程:
调用
main()
{
…ff(5); f=ff(4)*5; f=ff(3)*4; f=ff(2)*3; f=ff(1)*2;
120 24 6 2
返回
}
图5-5
5.6库函数
C语言定义了丰富的库函数,方便我们程序设计,我们只需注意以下几点,便可调用库函数。
u 函数的功能
u 函数的原型
u 库函数所需的头文件
我们已经知道,调用函数前,需要定义函数并声明函数,C系统为方便我们使用库函数,已经完成了这两项工作。首先函数按功能分为不同的库,如:数学函数、输入输出函数、字符串处理函数等,每个库都有一个头文件,给出了各库中各个函数的原型声明等有关信息。使用库函数之前,只需在程序中使用#include命令将其相应的头文件包含进来,程序就有了该函数的原型说明。然后在程序执行时,C系统会根据函数的原型说明,到相应的库文件中找到相应的库函数定义,并执行它。比如与数学函数对应的头文件是math.h, 与输入输出对应的头文件是stdio.h,与字符串处理函数对应的头文件是string.h等。
例5_11
#include “math.h”/*嵌入数学库函数头文件*/
main()
{ float f;
printf(“input a real number:”);
scanf(“%f”,&f);
printf(“The sqnare root is of %f is :% f”,f,sqrt(f));
} /* sqrt是数学函数库math.h头文件中的一个求开方的函数*/
库函数是一些被验证的、高效率的函数,进行程序设计时,应优先选用库函数。常用的库函数及标准头文件见附录。
注意,系统头文件在include子目录中,系统函数定义在lib子目录中,连接时,系统会根据原型到相应库文件中连接相应的库函数。要正确地进行文件包含和连接应设置正确的环境。如:
c:\tc\include
c:\tc\lib
这样才能在编译和连接时找到相应的文件。
5.7变量的作用域
5.7.1问题的提出
写一个函数,送入圆的半径r后,得到圆的面积及圆的周长。
5.7.2问题分析
我们已经知道,通过函数调用,最多可以用return语句返回一个值给调用函数,如果想返回多个值,怎么办呢?这道题涉及到变量的作用域。
5.7.3程序
例5_12 现在我们编写利用全局变量来完成写一个函数,送入圆的半径r后,得到圆的面积及圆的周长的程序。
float cl;/*定义全局变量c1*/
float carea(float r)
{float ar;
ar=3.14*r*r;
cl=2*3.14*r;/*使用全局变量c1*/
return ar;
}
main()
{float r,area;
printf(“\n r=?”);
scanf(“%f”,&r);
area=carea(r);
printf(“r=%5.2f,carae=%5.2f,cl=%5.2f\n”,r,area,cl); /*使用全局变量c1*/
}
程序运行结果:
r=?3 /* 输入半径3 */
r= 3.00,carae=28.26,c1=18.84
该程序中使用了在函数体外定义的全局变量c1, 在carea函数中把圆的周长存入c1中,在main函数中把c1的值输出,两个函数都在用c1变量,这是前面的程序中没有的情况,什么是全局变量和局部变量?它们有何特点?
5.7.4局部变量和全局变量
变量是程序设计中的重要元素,我们已经知道了它的一些属性:类型决定了它所获得的存储空间的大小;存入变量的值可以被多次使用,写入(赋值)将覆盖变量中原有的值;它所占居的存储空间的编号是变量的地址,此外,它还有存储属性,存储属性包括作用域与生存期两个方面。
变量的作用域就是变量在源程序代码中的可用范围,作用域根据变量的定义位置区分为局部变量和全局变量两种。在函数体内或语句块中定义的变量称局部变量,它们的作用范围只在所定义的函数体内或语句块中,在函数体外定义的变量,称全局变量,它的作用域从定义点起,直到本源文件结束,如果用extern关键词加以引用说明,它的作用范围可以扩大到在一个程序的所有文件中都可使用。如:图5-6。
int p=1,q=5;/*定义外部变量 */
外部变量p,q作用域
a,b,c变量作用域
float f1(int a) /*定义f1函数 */
{int b,c;
…
}
char c1,c2; /*定义外部变量 */
x,b,j变量作用域
外部变量c1,c2作用域
char f2(int x) /*定义f2函数 */
{int b,j;
…
}
m,n变量作用域
main()/*主函数 */
{int m,n;
…
}
图5-6
说明:
(1) 在函数体内定义的变量,只在本函数有效,因此在不同函数中可以使用相同名字的变量,如本例中f1、f2函数都有变量b,但两个函数中的b变量是不同的内存空间,它们互不相干。
(2) main函数中定义的变量(m,n),也只在main函数中有效,其他函数不得使用。
(3) 形参也是局部变量。
(4) 函数体外定义的变量都是全局变量(p,q,c1,c2),但根据定义点,作用范围不同。(c1,c2的作用范围小)
(5) 外部变量可以增加函数间的联系,但破坏了函数的封装性,增加了程序的难读性(程序中的函数都可以改变全局变量的值),建议少用全局变量。
5.8变量的存储类型
5.8.1问题
通过5次函数调用打印1到5的阶乘值。
5.8.2问题分析
该题有多种算法,其中效率较高的一种是:先求出1!=1,在1!的基础上再乘2,就得2!(2!=1!*2), 在2!的基础上再乘3,就得3!(3!=2!*3),…,这样我们要写的函数必须能保存上一次的阶乘值,在(n-1)!的基础上再乘n就完成了n!,问题是局部变量可以保存函数的结果,以备下一次调用函数时使用吗?换句话说在函数调用完成后它还存在吗?如果它不存在,显然它不能保存值。所以定义变量时,不仅要考虑它的作用域还要考虑它的生存期。
5.8.3 程序
例5_13 通过5次函数调用打印1到5的阶乘值
int fact(int x)
{ static int t=1;
t*=x;
return t;
}
main(){
int i;
for(i=1;i<=5;i++)
printf(“%d!=%d\n”,i,fact(i));}
程序运行结果为:
1!=1
2!=2
3!=6
4!=24
5!=120
思考:请将fact函数中的static int t;语句改为int t;,运行程序,结果是什么? main函数中不用循环语句,直接用printf(“%d!=%d\n”5,fact(5));语句,能输出5!的结果吗?为什么?
要完全看懂例5-13,必须知道变量的存储类型与变量的生存期。C语言把用户执行程序所占用的内存空间分为三部分:程序代码区、静态变量存储区和动态变量存储区。静态存储区中的变量在编译时创建,在程序结束时才被撤消。全局变量和静态变量放在该区,也就是说,在整个程序的执行期间它们始终存在。而存储在动态存储区中的变量,在程序的执行过程中根据需要创建,在运行完所在域后即被撤消,它们是动态存在的。局部变量和形参就分配在动态存储区。如图5-7。
程序代码区
静态存储区
动态存储取
存放可执行程序的机器指令
存放外部变量、静态局部变量
存放自动变量,形参等
全局变量或静态变量
局部变量和形参
图5-7
C语言定义一个变量的完整形式如下:
存储类型说明符 数据类型说明符 变量名
其中,存储类型说明符有:auto(自动)、extern(外部)、static(静态)、register(寄存器)四种。
5.8.4局部变量的存储类型
局部变量有三种存储类型:auto、 static、 register。
1.自动变量
局部变量定义时不指明存储类型或用auto说明时,都是自动变量。自动变量分配在动态存储区。程序执行到该局部变量所在域时,C系统为该局部变量分配存储空间,执行完该域后,释放其所在的空间。再次调用函数时,系统将为自动变量重新分配空间。如果自动变量定义时有初值,每调用一次函数,都要赋初值。没有赋初值的自动变量,将得到分配给它的内存单元原有的值,是一个不确定的值。
2.静态局部变量
使用static定义的局部变量,称静态局部变量,它的作用域与自动变量一样,但它被分配在静态存储区中,因此,当函数调用完成后,它依然存在,再次调用函数时,C系统不再重新为静态局部变量分配存储空间,赋给静态局部变量的初值是在编译时完成的,程序执行期间不再赋初值,因此局部静态变量可以保存上一次函数结束时的值,以备下一次函数调用时使用。对定义时未赋初值的静态局部变量,C系统在编译时自动赋初值一次,数值型赋0,字符型赋空格。
3.寄存器变量
使用register定义的局部变量,称寄存器变量,顾名思意,寄存器变量占用CPU的通用寄存器,而不占内存单元,因此使用寄存器变量就省去了访问内存的时间,从而提高了程序的执行速度。也由于这一特点,使用时请注意:
u 寄存器非常有限,不可能让变量长时间占有,所以寄存器变量只可能是自动变量,且程序中使用的寄存器变量的数目也是有限的。
u 由于长度的限制,寄存器变量不能是long、flot、doubleint类型。
u 寄存器变量没有地址,不能对它进行求地址运算。
那些被频繁使用,占用字节数又不多的变量,适合定义为寄存器变量。
例5_14 使用寄存器变量
main()
{ int s;
s=power(5,3);
printf(“%d \n”,s);
}
power(int x,register int n)/* n为寄存器变量 */
{ int p;
for(p=1;n;n--)
p*=x;
return p;
}
程序运行结果为:
125
n是寄存器变量,当传给n的值较大时,可以节省很多到内存存取变量值的时间。
5.8.5全局变量的存储类型
全局变量有两种存储类型:extern(外部)和static(静态)。
一个大的C程序由若干文件组成,为了增加联系,文件之间应该可以相互引用全局变量,如何引用全局变量呢?这就是我们要讨论的问题。
1.外部变量
没有用static定义的全局变量,就是外部变量,引用这种外部变量时,用extern加以说明,就可以使它的作用范围扩大到它所在的整个源程序文件,甚至其他文件。
例5_15 阅读下面的程序,注意外部变量的使用方法
/* 源程序文件f1.c*/
#include “f2.c”
extern int a=5;/*定义外部变量 ,extern可缺省*/
char c1=’a’,c2=’b’; /*定义外部变量 */
main()
{extern int b; /*外部变量b的定义在后,使用在前,必须用extern进行引用性声明 */
char c2=’B’;
printf(“%c \n”,c1-32);
printf(“%c ,%d\n”,c2,b*b);/*局部变量c2起作用*/
fact();
}
int b=2; /*定义外部变量b2*/
/* 源程序文件f2.c*/
extern int a;/*使用另一个源程序文件中定义的外部变量必须进行引用性声明*/
fact()
{int k,p=1;
for(k=1;k<=a;k++)
p*=k;
printf(“%d!=%d”,a,p);
}
使用外部变量时应注意以下问题:
u 在同一个源程序文件中使用外部变量时,如果使用在前,定义在后,必须用extern进行引用性声明。
u 使用另一个源程序文件中定义的外部变量,必须用extern声明后,才能使用。
u 当全局变量和局部变量名相同时,在局部变量的作用范围内,局部变量起作用,全局变量被屏蔽
extern有两种声明形式,定义性声明和引用性声明,定义性声明是为了建立实体,即建立变量的存储空间。引用性声明是为了建立标识符与实体之间的联系。
定义性声明的形式为:
[extern]类型 变量名[=初始化表达式];
定义在函数体外的变量都是外部变量,所以extern通常被缺省,初始化表达式被缺省时,C系统为数值型外部变量赋0,字符型赋空格。
引用性声明的形式为:
extern 类型 变量名;
定义性声明和引用性声明的区别如下:
u 定义性声明一定是在外部,引用性声明不限于外部,只要在使用该外部变量前声明即可。
u 定义性声明可以初始化,引用性声明不能初始化。
u 定义性声明在程序中只有一次,而引用性声明可以有多次。
2.静态全局变量
当用static定义全局变量时,该全局变量不允许其他文件引用,只能在定义它的源文件中使用,这种变量称静态全局变量。如上例中的外部变量a,若定义成:static int a;则f2.c文件不能引用。
C语言中的函数与全局变量类似,无论定义时是否用extern修饰,都是外部的,都具有静态生存期,若要使用在一个程序中的其他文件定义的函数,用extern进行引用性声明,如:
extern float ff(float);/*声明将引用一个其他文件中的函数*/
如果函数不允许其他文件引用,定义时在前面加static修饰,如:
static float fn(int n)/*定义一个不能被其他文件引用的函数。
{…
}
5.9编译预处理
在程序头部出现的以#开头,末尾没有分号的命令是编译预处理命令,C系统在进行实质性的编译前,即将源程序文件翻译成目标文件前,先要对这些命令进行处理,故而把它们叫做编译预处理命令,以区别于在程序执行时才起作用的执行语句。C提供的编译预处理命令有:
u 宏定义命令 #define
u 文件包含命令 #include
u 条件编译命令 #if #else #end if
u 行控制命令 #line
其他:#pragma、#error等
我们主要介绍前两种。
5.9.1宏定义命令
用一个宏名(标识符)来代表一个字符串,就叫宏定义,其形式如下:
# define 宏名 字符串
宏名用大写字母表示,以区别于变量,宏名后的字符串可以是表达式或语句,并可以进行嵌套定义(引用已定义的宏名)。
例5_16
#define X 5 /* 用X代替5 */
#define Y X+1 /* 用Y代替X+1(嵌套定义) */
#define Z Y*X/2 /* 用Z代替Y*X/2(嵌套定义) */
main()
{int a=Y; /* 替换为int a=5+1 */
printf(“Z=%d,”,Z); /* Z处替换为5+1*5/2 */
printf (“%d\n”,--a);
}
程序的运行结果为:
Z=7,5
宏定义命令可以带参数,带参数的宏定义的形式:
#define 宏名(参数表) 字符串
在字符串中应包含参数表中的参数。如:
#define CUBE(X) (X)*(X)(X)
…
int b=0,a=3;
b=CUBE(a);/* 替换为b=(a)*(a)*(a) */
使用有参数的宏时请注意几点:
(1)在实参数为表达式时,字符串中的参数是否用括号括起来,将影响替换结果,如上例改为:
#define CUBE(X) X*X*X
…
int b=2,a=3;
b=CUBE(b+a);/* 替换为b=b+a*b+a*b+a,而不是(b+a)*(b+a)*(b+a) */
(2)宏名与带参数的括号之间不应加空格,否则将空格以后的字符串都作为替代字符串的一部分,成为无参宏,如:
#define CUBE (X) (X)*(X)*(X)
int b=2,a=3;
b=CUBE(a);/* 替换为b=(X) (X)*(X)*(X)(a) */
带参数的宏与函数的主要区别是宏调用不需调用及返回时的开销,宏调用在编译时进行简单的字符串替换,宏调用多,将使编译时间增加,源程序增长。
5.9.2文件包含
文件包含的作用是将另一文件包含(嵌入)到本文件中,如图5-8所示:
目标文件
当前源程序文件
file.obj
file.c
#include “file1.h”
被包含的文件
file1.h
编译
A代码
B代码
图5-8
文件包含命令中,被包含文件用双引号括起来时,编译系统按以下方式搜索所要包含的文件:
源程序所在的文件目录中有所要的包含的文件?
Y
N
按标准方式搜索其他目录
嵌入该文件
图5-9
被包含文件用尖括号括起来时,编译系统只按标准方式搜索其他目录,搜索范围较小,但搜索时间短。
注意,一个#include命令只能包含一个文件,文件包含可以嵌套使用,即一个包含文件又包含另一个文件,当被包含文件的内容被修改后,包含这个文件的源程序文件要重新编译。
5.10语法练习
1. 选择题
(1) 下面哪个是正确的函数定义
A double fun(int x, int y) B double fun(int x; int y)
C double fun(int x, y) D double fun(int x, y;)
(2) 缺省修饰符的情况下 ,函数自身是____。
A. static B. auto C. register D. extern
(3) 下面不正确的说法是____。
A.C程序通常是由许多小函数组成的,而不是少量的大函数组成的.
B. 在源文件中可以以不同的顺序定义函数。
C. 通常调用函数前,函数必须被定义和声明。
D. dummy(){ } 是无用的函数。
(4) 选择程序的结果___
increment()
{ static int x=0; x+=1;printf("%d " , x ); }
main()
{ increment(); increment(); increment(); }
A. 1 1 1 B. 1 2 3 C. 0 1 2 D. 0 0 0
(5) 选择程序的结果___
main()
{ static char a[80] = "AB" , b[80] = "LAMP" ; int i = 0;
展开阅读全文