1、项目五 使用函数调用各功能模块v教学目的:教学目的:通过本章的学习,要求能熟练掌握函数的定义和调用通过本章的学习,要求能熟练掌握函数的定义和调用方法,掌握函数的嵌套调用和递归调用,理解变量的作用方法,掌握函数的嵌套调用和递归调用,理解变量的作用域和存储类别,掌握内部函数和外部函数,并能够在不同域和存储类别,掌握内部函数和外部函数,并能够在不同情况下灵活选择函数来解决实际问题。掌握编译预处理命情况下灵活选择函数来解决实际问题。掌握编译预处理命令的使用方法。函数和预处理命令是编写模块化程序的重令的使用方法。函数和预处理命令是编写模块化程序的重要方法,这将为编写比较复杂的程序设计的学习打下基础。要方
2、法,这将为编写比较复杂的程序设计的学习打下基础。v教学内容教学内容 项目五 使用函数调用各功能模块函数函数编译预处理编译预处理函数的定义函数的定义函数调用函数调用变量的作用域和存储类别变量的作用域和存储类别内部函数和外部函数内部函数和外部函数文件包含命令文件包含命令宏定义宏定义条件编译条件编译项目五 使用函数调用各功能模块重点:重点:()函数的定义和函数调用)函数的定义和函数调用(2 2)函数的参数传递)函数的参数传递难点难点 :、函数的参数传递、函数的参数传递、递归调用、递归调用、变量的作用域和存储特性、变量的作用域和存储特性v重点和难点重点和难点任务使用函数统计课程分数信息辅导员辅导员张老
3、师在使用小王设计的程序时,发现他分别要对每门课程学生成张老师在使用小王设计的程序时,发现他分别要对每门课程学生成绩的总分及平均分进行计算,如果这样的计算过程需要重复使用或者在其他场绩的总分及平均分进行计算,如果这样的计算过程需要重复使用或者在其他场合中多次使用,使用以前的方法将使得程序代码重复书写,在本任务中我们使合中多次使用,使用以前的方法将使得程序代码重复书写,在本任务中我们使用函数来统计课程分数信息,可以减少编程工作量,完善了原来的程序,帮助用函数来统计课程分数信息,可以减少编程工作量,完善了原来的程序,帮助张老师解决了该问题。张老师解决了该问题。任务使用函数统计课程分数信息/*功能:使
4、用函数统计课程分数信息*/#include#define MAX 1000#define M 100float sumM,aveM;int i,j;int count;int course;float aMAXM;void input()printf(请输入课程的门数:);scanf(%d,&course);printf(请输入学生总人数:);scanf(%d,&count);printf(“请输入每个学生的课程成绩:”);任务使用函数统计课程分数信息for(i=0;icount;i+)for(j=0;jcourse;j+)scanf(%f,&aij);void sum_ave(int s,i
5、nt r)for(i=0;ir;i+)sumi=0.0;for(j=0;js;j+)任务使用函数统计课程分数信息 sumi=sumi+aji;avei=sumi/s;void output()for(i=0;i0)return(1);if(x=0)return(0););if(x b)c=1;else if(a=b)c=0;else c=-1;return c;编译、连接、和运行程序。程序运行后,屏幕显示:编译、连接、和运行程序。程序运行后,屏幕显示:5.4 函数调用 5.4.1 函数调用的一般形式如果按自左向右顺序求实参的值,则函数调用相当于如果按自左向右顺序求实参的值,则函数调用相当于co
6、mpare(2,3),程序运行的结果应为程序运行的结果应为”-1”。若按自右向左。若按自右向左顺序求实参的值,则相当于顺序求实参的值,则相当于compare(3,3),程序运行结果程序运行结果为为”0”。如果不清楚自己所用的编译器对实参的求值顺序,。如果不清楚自己所用的编译器对实参的求值顺序,用上述代码上机一试就清楚了。用上述代码上机一试就清楚了。注意:由于不同的编译器对实参的求值顺序不一样,为了注意:由于不同的编译器对实参的求值顺序不一样,为了使程序的通用性不受影响以及避免大家对同一段代码产生不同使程序的通用性不受影响以及避免大家对同一段代码产生不同的理解,应尽量避免使用这种容易混淆的用法。
7、的理解,应尽量避免使用这种容易混淆的用法。5.4 函数调用函数出现在一个表达式中,这种表达式称为函数表达式。函数出现在一个表达式中,这种表达式称为函数表达式。这时要求函数带回一个确定的值以参加表达式的运算。例如:这时要求函数带回一个确定的值以参加表达式的运算。例如:s=sum(a,b)+sum(x,y);1.1.函数语句函数语句按函数在程序中出现的位置来分,可以有一下三种函数按函数在程序中出现的位置来分,可以有一下三种函数调用的方式。调用的方式。把函数调用作为一个语句。如例把函数调用作为一个语句。如例5.15.1中的中的printstartprintstart();();这时不要求函数带返回值
8、,只要求函数完成一些操作。这时不要求函数带返回值,只要求函数完成一些操作。2.2.函数表达式函数表达式5.4 函数调用 5.4.2 函数调用的方式 函数调用作为一个函数的参数,例如:函数调用作为一个函数的参数,例如:s=sum(a,sum(b,c);其中,其中,sum(b,c)是一次函数调用,它的值作为是一次函数调用,它的值作为sum另另一次调用的参数。一次调用的参数。s的值为的值为a,b,c三数的总和。三数的总和。其实,函数调用作为函数的参数,也是函数表达式调用的其实,函数调用作为函数的参数,也是函数表达式调用的一种形式,因为函数参数本身就是一个表达式的形式。一种形式,因为函数参数本身就是一
9、个表达式的形式。3.3.函数参数函数参数函数函数sum是表达式的一部分,将是表达式的一部分,将sum(a,b)的值加上的值加上sum(x,y)的和赋值给的和赋值给s。5.4 函数调用如果一个函数要调用另外一个函数,首先是被调用的函数如果一个函数要调用另外一个函数,首先是被调用的函数必须存在。其次还应在主调函数中对所有被调函数加以说明,必须存在。其次还应在主调函数中对所有被调函数加以说明,否则,在连接时会出现找不到所调用函数的错误信息。同变量否则,在连接时会出现找不到所调用函数的错误信息。同变量一样,函数的调用也应遵循一样,函数的调用也应遵循“先定义后使用先定义后使用”的原则。的原则。对被调函数
10、的声明分为两种情况:对被调函数的声明分为两种情况:(1)如果被调函数是)如果被调函数是C语言系统提供的标准库函数,则语言系统提供的标准库函数,则在源程序文件的开头处,使用在源程序文件的开头处,使用#include命令,将存放所调用命令,将存放所调用库函数的有关库函数的有关“头文件头文件”包含到该程序文件中来。包含到该程序文件中来。5.4 函数调用 5.4.3 对被调用函数的声明和函数原型#include命令的一般形式为:命令的一般形式为:#include或或#include stdio.h(2)如果被调用函数为用户自己定义的函数,一般情况)如果被调用函数为用户自己定义的函数,一般情况下,应在主
11、调函数中对被调用函数(返回值)的类型进行说明。下,应在主调函数中对被调用函数(返回值)的类型进行说明。函数的说明方法是:在主调函数的声明部分对被调函数进行声函数的说明方法是:在主调函数的声明部分对被调函数进行声明。在主调函数中对被调函数作说明的目的是使编译系统知道明。在主调函数中对被调函数作说明的目的是使编译系统知道被调函数返回值的类型,以便在主调函数中按此种类型对返回被调函数返回值的类型,以便在主调函数中按此种类型对返回值作相应的处理。值作相应的处理。5.4 函数调用 5.4.3 对被调用函数的声明和函数原型其一般形式为:其一般形式为:类型说明符类型说明符 被调函数名被调函数名(类型类型 形
12、参,类型形参,类型 形参形参);或者:或者:类型说明符类型说明符 被调函数名被调函数名(类型,类型类型,类型);括号内给出了形参的类型和形参名,或只给出形参类型。括号内给出了形参的类型和形参名,或只给出形参类型。这便于编译系统进行检错,以防止可能出现的错误。这便于编译系统进行检错,以防止可能出现的错误。5.4 函数调用 5.4.3 对被调用函数的声明和函数原型例例5.1 main函数对函数对printstart()函数的说明为:函数的说明为:void printstart();例例5.2 main函数对函数对sum()函数的说明为:函数的说明为:int sum(int x,int y);也可以
13、写成:也可以写成:int sum(int,int);5.4 函数调用 5.4.3 对被调用函数的声明和函数原型语言中规定在以下几种情况时可以省去在主调函数中对语言中规定在以下几种情况时可以省去在主调函数中对被调函数的函数说明。被调函数的函数说明。1)当被调函数的返回值是整型或字符型时,可以不对被调当被调函数的返回值是整型或字符型时,可以不对被调函数作说明。这时系统会自动对被调函数返回值按整型处理。函数作说明。这时系统会自动对被调函数返回值按整型处理。例例5.3的主函数中把函数声明语句的主函数中把函数声明语句int compare(int a,int b);注释掉而直接调用就属于这种情况。注释掉
14、而直接调用就属于这种情况。5.4 函数调用 5.4.3 对被调用函数的声明和函数原型2)2)当被调函数的函数定义出现在主调函数之前时,在主调当被调函数的函数定义出现在主调函数之前时,在主调函数中也可以不对被调函数再作说明而直接调用。例如例函数中也可以不对被调函数再作说明而直接调用。例如例5.15.1中,中,函数函数printstartprintstart()()的定义放在的定义放在 menu()menu()函数之前,因此可在函数之前,因此可在 menu()menu()函数中省去对函数中省去对printstartprintstart()()函数的函数说明函数的函数说明void void prin
15、tstartprintstart();();3)3)如在所有函数定义之前,在函数外预先说明了各个函数如在所有函数定义之前,在函数外预先说明了各个函数的类型,则在以后的各主调函数中,可以不再对被调函数作说的类型,则在以后的各主调函数中,可以不再对被调函数作说明。明。例如:例如:long factor(long factor(intint a);a);long sum(long sum(intint b);b);5.4 函数调用 5.4.3 对被调用函数的声明和函数原型void main()long factor(int a)long sum(int b)其中第一、二行对其中第一、二行对facto
16、r函数和函数和sum函数预先作了说明。函数预先作了说明。因此在以后各函数中无须对因此在以后各函数中无须对factor和和sum函数再作说明就可函数再作说明就可直接调用。直接调用。5.5 函数的嵌套调用和递归调用 【例例5.45.4】计算计算 =1=1!+2+2!+n+n!(n1(n1,2020的整数,从键盘输入的整数,从键盘输入)。算法设计要点:本案例可以设计算法设计要点:本案例可以设计2 2个函数:个函数:factor()factor()用于求用于求n n!;!;sum()sum()通过调用通过调用factor()factor()来实现求来实现求。5.5.1 5.5.1 函数的嵌套调用实例函
17、数的嵌套调用实例 /*源文件名:源文件名:Li5_4.c Li5_4.c 功能:求阶乘和功能:求阶乘和 */#include#include stdio.hstdio.h long factor(long factor(intint n)n)/*定义求阶乘函数定义求阶乘函数factor()factor()*/intint i i;long f=1;long f=1;for(for(i i=1;i=1;i=n;in;i+)+)f=ff=f*i i;return f;return f;5.5 函数的嵌套调用和递归调用 5.5.1 函数的嵌套调用实例long sum(int m)/*定义求和函数su
18、m()*/int i;long s=0;for(i=1;i1)if(n1)f=nf=n*factor(n-1);factor(n-1);else else f=1;f=1;return f;return f;void main()void main()intint n;n;long s;long s;printfprintf(please input a number:);(please input a number:);scanfscanf(%(%d,&nd,&n););s=factor(n);s=factor(n);/*调用调用factor()factor()函数函数*/printfpri
19、ntf(%d!=%ld(%d!=%ldn,n,sn,n,s););5.5 函数的嵌套调用和递归调用其中,其中,factorfactor函数在定义的过程中调用了本身,这种情况函数在定义的过程中调用了本身,这种情况叫做函数的递归调用。叫做函数的递归调用。5.5.3 5.5.3 函数的递归调用实例函数的递归调用实例编译、连接、和运行程序。程序运行后,屏幕显示:编译、连接、和运行程序。程序运行后,屏幕显示:5.5 函数的嵌套调用和递归调用一个函数在它的函数体内调用它自身称为递归调用。这种一个函数在它的函数体内调用它自身称为递归调用。这种函数称为递归函数。语言允许函数的递归调用。在递归调用函数称为递归函
20、数。语言允许函数的递归调用。在递归调用中,主调函数又是被调函数。执行递归函数将反复调用其自身,中,主调函数又是被调函数。执行递归函数将反复调用其自身,每调用一次就进入新的一层。每调用一次就进入新的一层。下面以例下面以例5.55.5说明一下递归的执行过程。说明一下递归的执行过程。设执行本程序时输入为设执行本程序时输入为5 5,即求,即求5!5!。在主函数中的调用语句。在主函数中的调用语句即为即为s=factor(5);s=factor(5);,进入,进入factorfactor函数后,由于函数后,由于n=5,n=5,大于大于1 1,故,故应执行应执行f=nf=n*factor(nfactor(n
21、*1),1),即即f=factor(5-1)f=factor(5-1)*5 5。该语句对。该语句对factorfactor函数作递归调用即函数作递归调用即factor(4)factor(4)。5.5.4 5.5.4 函数的递归调用说明函数的递归调用说明进行四次递归调用后,进行四次递归调用后,factorfactor函数形参取得的值变为函数形参取得的值变为1 1,故不再继续递归调用而开始逐层返回主调函数。故不再继续递归调用而开始逐层返回主调函数。factor(1)factor(1)的的函数返回值为函数返回值为1 1,factor(2)factor(2)的返回值为的返回值为2 2*1=21=2,f
22、actor(3)factor(3)的的返回值为返回值为3 3*2=62=6,factor(4)factor(4)的返回值为的返回值为4 4*6=246=24,最后返回值,最后返回值factor(5)factor(5)为为5 5*24=12024=120。注意:为了防止递归调用无终止地进行,必须在函数内有注意:为了防止递归调用无终止地进行,必须在函数内有终止递归调用的手段。常用的方法是加条件对递归是否继续进终止递归调用的手段。常用的方法是加条件对递归是否继续进行判断,满足某种条件后就不再作递归调用,而是逐层返回。行判断,满足某种条件后就不再作递归调用,而是逐层返回。如例如例5.55.5中的条件中
23、的条件if(n1)if(n1)就是控制递归继续的条件,当就是控制递归继续的条件,当n n不小不小于于1 1的时候递归就终止,开始回朔的过程。的时候递归就终止,开始回朔的过程。5.5 函数的嵌套调用和递归调用 5.5.4 函数的递归调用说明5.6 内部函数和外部函数一个函数如果只能被本文件中其它函数所调用,称为内部一个函数如果只能被本文件中其它函数所调用,称为内部函数,内部函数又称静态函数。在定义内部函数时在函数名和函数,内部函数又称静态函数。在定义内部函数时在函数名和函数类型前面加函数类型前面加static。即:。即:static 函数类型函数类型 函数名(形参列表)函数名(形参列表)函数体函
24、数体5.6.1 5.6.1 内部函数内部函数5.5 内部函数和外部函数 5.1 内部函数例:例:static int fun(int a,int b)特点:只能被本文件中的函数所调用。特点:只能被本文件中的函数所调用。优点:不用担心与其它源文件中的函数同名,因为即使同优点:不用担心与其它源文件中的函数同名,因为即使同名也没关系。名也没关系。一个函数可以被其它文件中其它函数所调用,就称为外部一个函数可以被其它文件中其它函数所调用,就称为外部函数。在定义函数时可冠以关键字函数。在定义函数时可冠以关键字extern(省略也可),表(省略也可),表示此函数是外部函数。即:示此函数是外部函数。即:ext
25、ern 函数类型函数类型 函数名(形参列表)函数名(形参列表)函数体函数体如:如:extern int fun(int a,int b)5.5 内部函数和外部函数5.6.2 5.6.2 外部函数外部函数5.6 内部函数和外部函数 5.6.2 外部函数特点:允许被所有源文件中的函数所调用。特点:允许被所有源文件中的函数所调用。注意:调用其它源文件中的外部函数时,需要对其进行说注意:调用其它源文件中的外部函数时,需要对其进行说明。明。【例例5.6】以多文件的形式实现加、减、乘、除和求余数以多文件的形式实现加、减、乘、除和求余数运算程序运算程序说明:将实现加、减、乘、除和求余数运算的程序段分别说明:
26、将实现加、减、乘、除和求余数运算的程序段分别作为作为1个独立的函数、存储在个独立的函数、存储在1个独立的源文件中。个独立的源文件中。5.6 内部函数和外部函数 5.6.2 外部函数程序框架如下程序框架如下(完整程序详见完整程序详见【例例5.65.6】源代码源代码):分别创建分别创建addition.caddition.c源文件、源文件、subtraction.csubtraction.c源文件、源文件、multiplication.cmultiplication.c源文件、源文件、division.cdivision.c源文件、源文件、remainder.cremainder.c源文件,分别在
27、各源文件中实现相应的加、减、乘、除和求余源文件,分别在各源文件中实现相应的加、减、乘、除和求余数函数的功能。数函数的功能。编译、连接、和运行程序。程序运行后,屏幕显示:编译、连接、和运行程序。程序运行后,屏幕显示:5.6 内部函数和外部函数 5.6.2 外部函数5.6 内部函数和外部函数在软件工程项目中,采用结构化方法进行程序设计与编程,在软件工程项目中,采用结构化方法进行程序设计与编程,通常会产生多个源文件(例如源程序文件、数据结构定义文件通常会产生多个源文件(例如源程序文件、数据结构定义文件等)。等)。那么,如何将这些源文件编译、连接成一个统一的可执行那么,如何将这些源文件编译、连接成一个
28、统一的可执行文件呢?文件呢?5.6.3 5.6.3 多个源文件的编译与连接多个源文件的编译与连接5.6 内部函数和外部函数 5.6.3 多个源文件的编译与连接一般有两种方法:一般有两种方法:1.1.分别编译、一并连接分别编译、一并连接C C编译程序是以源文件为编译单位。编译程序是以源文件为编译单位。当一个程序中的函数和数据结构分放在多个源文件中时,当一个程序中的函数和数据结构分放在多个源文件中时,先将各文件分别编译,再通过先将各文件分别编译,再通过linklink命令产生一个可执行文件命令产生一个可执行文件(.exe)(.exe)。2.2.集中编译、连接集中编译、连接利用编译预处理命令利用编译
29、预处理命令#include#include,将其它源文件包含到主函,将其它源文件包含到主函数数main()main()所在的源文件的开头,然后直接编译该文件即可。所在的源文件的开头,然后直接编译该文件即可。5.6 内部函数和外部函数 5.6.3 多个源文件的编译与连接【例例5.75.7】先通过添加源文件的操作将先通过添加源文件的操作将addition.caddition.c源文件,源文件,subtraction.csubtraction.c源文件,源文件,multiplication.cmultiplication.c源文件,源文件,division.cdivision.c源文件,源文件,re
30、mainder.cremainder.c源文件添加到本工程(加减乘除求余)来源文件添加到本工程(加减乘除求余)来,然后在其主函数,然后在其主函数main()main()里添加如下几行:里添加如下几行:/*源文件名:源文件名:Li5_7.cLi5_7.c 功能:以多文件的形式实现加、减、乘、除和求余数运算功能:以多文件的形式实现加、减、乘、除和求余数运算*/*将其它各源文件包含进来将其它各源文件包含进来*/#include#include addition.caddition.c#include#include subtraction.csubtraction.c#include#include
31、 multiplication.cmultiplication.c#include#include division.cdivision.c#include#include remainder.cremainder.c void main()void main()5.7变量的作用域 局部变量也称为内部变量。局部变量是在函数内(包括局部变量也称为内部变量。局部变量是在函数内(包括函数说明和函数体)作定义说明的,其作用域仅限于函数内,函数说明和函数体)作定义说明的,其作用域仅限于函数内,离开该函数后再使用这种变量是非法的。离开该函数后再使用这种变量是非法的。5.7.1 5.7.1 局部变量局部变量
32、 【例5.8】局部变量的作用域 /*源文件名:Li5_8.c 功能:测试局部变量的作用域 */#include stdio.hvoid test(int a)int b=20;printf(%dn,a+b);5.7变量的作用域 5.7.1 局部变量void main()void main()intint i i=2,j=3,k;=2,j=3,k;k=k=i+ji+j;intint k=8;k=8;printfprintf(%d(%dn,kn,k););printfprintf(%d(%dn,kn,k););test(k);test(k);5.7变量的作用域 5.7.1 局部变量在函数在函数te
33、sttest内定义了三个变量,内定义了三个变量,a a为形参,为形参,b b为一般变量。为一般变量。在在 testtest的范围内的范围内a a、b b有效,或者说有效,或者说a a、b b变量的作用域限于变量的作用域限于testtest内。同理,内。同理,i,j,ki,j,k的作用域限于的作用域限于mainmain内。内。编译、连接、和运行程序。程序运行后,屏幕显示:编译、连接、和运行程序。程序运行后,屏幕显示:5.7变量的作用域 5.7.1 局部变量关于局部变量的作用域还要说明以下几点:关于局部变量的作用域还要说明以下几点:(1)1)主函数中定义的变量只能在主函数中使用,不能在其主函数中定
34、义的变量只能在主函数中使用,不能在其它函数中使用。同时,主函数中也不能使用其它函数中定义的它函数中使用。同时,主函数中也不能使用其它函数中定义的变量。因为主函数也是一个函数,它与其它函数是平行关系。变量。因为主函数也是一个函数,它与其它函数是平行关系。这一点是与其它语言不同的,应予以注意。这一点是与其它语言不同的,应予以注意。(2)2)形参变量是属于被调函数的局部变量,实参变量是属形参变量是属于被调函数的局部变量,实参变量是属于主调函数的局部变量。于主调函数的局部变量。(3)3)允许在不同的函数中使用相同的变量名,它们代表不允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元
35、,互不干扰,也不会发生混淆。同的对象,分配不同的单元,互不干扰,也不会发生混淆。(4)(4)在复合语句中也可定义变量,其作用域只在复合语句在复合语句中也可定义变量,其作用域只在复合语句范围内。范围内。6.7变量的作用域全局变量也称为外部变量,它是在函数外部定义的变量。全局变量也称为外部变量,它是在函数外部定义的变量。它不属于哪一个函数,它属于整个源程序文件。其作用域是它不属于哪一个函数,它属于整个源程序文件。其作用域是整个源程序。在函数中使用全局变量,一般应作全局变量说整个源程序。在函数中使用全局变量,一般应作全局变量说明。明。只有在函数内经过说明的全局变量才能使用。全局变量只有在函数内经过说
36、明的全局变量才能使用。全局变量的说明符为的说明符为extern。但在一个函数之前定义的全局变量,。但在一个函数之前定义的全局变量,在该函数内使用可不再加以说明。在该函数内使用可不再加以说明。5.7.2 5.7.2 全局变量全局变量5.7变量的作用域 5.7.2 全局变量【例例5.95.9】要求设计一个函数要求设计一个函数cuboidcuboid(double length,double length,double width,double heightdouble width,double height)()(3 3个参数依次为长方体的个参数依次为长方体的长、宽、高),用于求长方体的体积及正、
37、侧、顶三个面的长、宽、高),用于求长方体的体积及正、侧、顶三个面的面积。行数据共享。面积。行数据共享。/*源文件名:源文件名:Li5_9.cLi5_9.c 功能:求长方体的体积及正、侧、顶三个面的面积功能:求长方体的体积及正、侧、顶三个面的面积 */#include#include stdio.hstdio.h double area1,area2,area3;/double area1,area2,area3;/*定义定义3 3个外部变量,用于数据共享个外部变量,用于数据共享*/double double cuboidcuboid(double length,double width,dou
38、ble height);(double length,double width,double height);/*函数说明函数说明*/5.7变量的作用域 5.7.2 全局变量void main()double volume,length,width,height;printf(please input the cuboids length、width and height:);scanf(%lf%lf%lf,&length,&width,&height);volume=cuboid(length,width,height);printf(“n volume=%.2lf,area1=%.2lf,
39、area2=%.2lf,area3=%.2lfn,volume,area1,area2,area3);double cuboid(double length,double width,double height)double volume;volume=length*width*height;/*计算体积*/area1=length*width;/*计算3个面的面积*/5.7变量的作用域 5.7.2 全局变量area2=width*height;area3=length*height;return(volume);/*返回体积值*/编译、连接、和运行程序。程序运行后,屏幕显示:编译、连接、和运
40、行程序。程序运行后,屏幕显示:5.7变量的作用域 5.7.2 全局变量注意:注意:(1)(1)外部变量的作用域:从定义点到本文件结束。外部变量的作用域:从定义点到本文件结束。为方便使用,建议将外部变量的定义放在文件开头,为方便使用,建议将外部变量的定义放在文件开头,如例如例.9.9所示。所示。(2)(2)在同一源文件中,允许外部变量和内部变量同名。在同一源文件中,允许外部变量和内部变量同名。在内部变量的作用域内,外部变量不起作用。在内部变量的作用域内,外部变量不起作用。(3)(3)外部变量可实现函数之间的数据共享,但又使这些函外部变量可实现函数之间的数据共享,但又使这些函数依赖这些外部变量,因
41、而使得这些函数的独立性降低。数依赖这些外部变量,因而使得这些函数的独立性降低。从模块化程序设计观点来看,这是不利的。因此不是非用不可从模块化程序设计观点来看,这是不利的。因此不是非用不可时,不要使用外部变量。时,不要使用外部变量。5.8 变量的存储类别 语言中的变量,不仅有类型特性,还有存储特性,从语言中的变量,不仅有类型特性,还有存储特性,从变量值存在的时间(即生存期)角度来分,可以分为静态存变量值存在的时间(即生存期)角度来分,可以分为静态存储方式和动态存储方式。储方式和动态存储方式。1动态存储方式:动态存储方式:是在程序运行期间根据需要进行动是在程序运行期间根据需要进行动态的分配存储空间
42、的方式。态的分配存储空间的方式。自动内部变量自动内部变量(auto)、寄存器变量、寄存器变量(register)5.8.1 5.8.1 动态存储和静态存储动态存储和静态存储5.8 变量的存储类别 5.8.1 动态存储和静态存储2 2静态存储方式:静态存储方式:是指在程序运行期间分配固定的存储是指在程序运行期间分配固定的存储空间的方式。静态内部变量空间的方式。静态内部变量(static)(static)、外部变量、外部变量(extern)(extern)。用户存储空间可以分为三个部分:用户存储空间可以分为三个部分:(1)(1)程序区;程序区;(2)(2)静态存储区;静态存储区;(3)(3)动态存
43、储区;动态存储区;5.8 变量的存储类别 5.8.1 动态存储和静态存储全局变量全部存放在静态存储区,在程序开始执行时给全全局变量全部存放在静态存储区,在程序开始执行时给全局变量分配存储区,程序运行结束就释放。在程序执行过程中局变量分配存储区,程序运行结束就释放。在程序执行过程中它们占据固定的存储单元,而不动态地进行分配和释放;它们占据固定的存储单元,而不动态地进行分配和释放;动态存储区存放以下数据:动态存储区存放以下数据:1)1)函数形式参数;函数形式参数;2)2)自动变量(未加自动变量(未加staticstatic声明的局部变量);声明的局部变量);3)3)函数调用时的现场保护和返回地址;
44、函数调用时的现场保护和返回地址;对以上这些数据,在函数开始调用时分配动态存储空间,函数对以上这些数据,在函数开始调用时分配动态存储空间,函数结束时释放这些空间。结束时释放这些空间。5.8 变量的存储类别5.8.2 5.8.2 动态存储动态存储 【例5.10】动态存储举例 /*源文件名:Li5_10.c 功能:测试动态存储变量的空间分配情况 */#include stdio.hvoid test()int m=10;m+;printf(m=%dn,m);5.8 变量的存储类别 5.8.2 动态存储void main()void main()printfprintf(the first time:
45、);(the first time:);test();test();printfprintf(the second time:);(the second time:);test();test();printfprintf(the third time:);(the third time:);test();test();5.8 变量的存储类别 5.8.2 动态存储编译、连接、和运行程序。程序运行后,屏幕显示:编译、连接、和运行程序。程序运行后,屏幕显示:5.8 变量的存储类别 5.8.2 动态存储函数中的局部变量,如不特别声明为函数中的局部变量,如不特别声明为staticstatic存储类别,存
46、储类别,都是动态地分配存储空间的,数据存储在动态存储区中。函都是动态地分配存储空间的,数据存储在动态存储区中。函数中的形参和在函数中定义的变量(包括在复合语句中定义数中的形参和在函数中定义的变量(包括在复合语句中定义的变量)都属此类,在调用该函数时系统会给它们分配存储的变量)都属此类,在调用该函数时系统会给它们分配存储空间,在函数调用结束时就自动释放这些存储空间。这类局空间,在函数调用结束时就自动释放这些存储空间。这类局部变量称为自动变量。自动变量用关键字部变量称为自动变量。自动变量用关键字autoauto(autoauto可以省可以省略)作存储类别的声明。略)作存储类别的声明。5.8 变量的
47、存储类别 5.8.2 动态存储如例如例5.105.10中,中,testtest函数中的函数中的m m没有用没有用staticstatic声明,说声明,说明它就是一个自动变量,所以,在主函数中调用了三次的明它就是一个自动变量,所以,在主函数中调用了三次的testtest函数,每次得到的函数,每次得到的m m的输出值都是一样的,这是因为的输出值都是一样的,这是因为自动变量只有在函数被调用的时候分配空间,当函数调用自动变量只有在函数被调用的时候分配空间,当函数调用结束,空间也就自动释放了,所以每次调用结束,空间也就自动释放了,所以每次调用m m都是被重新都是被重新赋初始值赋初始值1010。5.8 变
48、量的存储类别5.8.3 5.8.3 用用staticstatic声明的局部变量声明的局部变量 【例5.11】静态局部变量例题 /*源文件名:Li5_11.c功能:测试静态局部变量的空间分配情况 */#include stdio.hvoid test()static int m=10;/定义m为静态局部变量m+;printf(m=%dn,m);5.8 变量的存储类别 5.8.3 用static声明的局部变量void main()printf(the first time:);test();printf(the second time:);test();printf(the third time:
49、);test();5.8 变量的存储类别 5.8.3 用static声明的局部变量有时希望函数中的局部变量的值在函数调用结束后不消失而有时希望函数中的局部变量的值在函数调用结束后不消失而保留原值,这时就应该指定局部变量为保留原值,这时就应该指定局部变量为“静态局部变量静态局部变量”,用关,用关键字键字staticstatic进行声明。如例进行声明。如例6.116.11中中testtest函数中的函数中的m m变量被用变量被用staticstatic声明位静态局部变量。由于静态变量在程序运行期间的被声明位静态局部变量。由于静态变量在程序运行期间的被分配的存储空间是固定的。所以第一次调用分配的存储
50、空间是固定的。所以第一次调用testtest函数的时候,函数的时候,m m变量空间被分配,并且赋予初值变量空间被分配,并且赋予初值1010,然后执行,然后执行m+m+操作,所以操作,所以m m的的值就为值就为11.11.但调用结束的时候但调用结束的时候m m的空间仍然存在,没有因函数调用的空间仍然存在,没有因函数调用结束而被释放。所以在结束而被释放。所以在testtest函数第二次被调用的时候,函数第二次被调用的时候,m m不再重不再重新分配空间和初始化为新分配空间和初始化为1010,而是使用原来的空间,沿用上一次的,而是使用原来的空间,沿用上一次的值值1111,故第二次调用的时候输出的,故第