1、1,C+语言程序设计,林桂明,2,第5章 函数,5.1、函数的定义和说明,5.2、函数的调用,5.4、函数的参数,5.5、函数重载,5.6、内联函数,5.3、函数原型,5.7、递归函数,5.8、作用域和存储类型,3,教学目的及重点难点,1掌握函数的一般结构及函数的定义方法; 2掌握两种形式的函数调用(表达式中的函数调用和语句中的函数调用),掌握函数调用过程中参数传递的原理; 3了解局部变量、全局变量和变量的存储类型的概念; 4掌握函数重载的方法; 5掌握内联函数的含义与使用; 6熟悉常用的字符串操作函数; 7了解函数和变量的作用域与生存期;,重点: 1、函数的声明和函数调用; 2、函数调用的三
2、种参数传递方式。 难点:函数调用的三种参数传递方式、递归的概念和递归函数设计、函数重载的概念。,4,5.1 函数定义,什么是函数C+中把由相关的语句组织在一起、有自己的名称、实现独立功能、能在程序中使用的这种程序块称为函数。 C+函数与函数之间通过输入参数和返回值(输出)来联系。可以把函数看作是一个“黑盒”,除了输入输出,其他什么也看不见。 函数的分类:,main函数,其他函数,从程序角度,库函数,自定义函数,从用户角度,无参函数,有参函数,从表现形式,5,5.1 函数定义,(一)函数的定义 一般语法格式:,合法标识符,函数返回值类型 无返回值void,函数体,函数类型 函数名(形参列表) 函
3、数体 ,说明: 1、形参表是用逗号分隔的一组变量说明,包括形参的类型和形参名。形参将在函数被调用时从调用函数那里得到数据。形参表可以为空。 格式如下: 类型1 形参1 ,类型2 形参2 , ,类型n 形参n 例:void f1(int x,int y) coutx“,”yendl; ,函数头部,6,5.1 函数定义,2、函数的返回值 函数的返回值就是函数执行后带回的一个结果。它通过函数体中的return语句获得。无返回值的函数(void类型),不必写return语句。 return语句的一般格式如下:return 表达式; 或 return(表达式); 其功能是将表达式的值作为函数的返回值并带
4、回主调函数。同时将程序的控制权由被调用函数转给调用函数。,求两个实数的最大数max()。设两个数为x,y,函数定义如下: float max(float x,float y) float m; m=xy?x:y; return m; ,float max(float x,float y) if (xy)return x; elsereturn y; ,void print(int n) for(int I=1; I=n;I+)cout*; ,7,5.1 函数定义,例:编写一个函数,以整型数作为形参,当该参数为奇数时返回false,而当该参数为偶数时返回true。,练习:编写一个计算矩形面积的函
5、数。,bool f1(int n) if (n%2= =0)return true;elsereturn false; ,8,5.2 函数调用,(二)函数的调用 在一个函数a中调用另一函数b时,我们称函数a为主调函数,函数b为被调函数。 (1) 函数调用 函数调用就是对已定义函数的具体应用。一般形式如下:函数名(实参列表); 实参是用来在调用函数时对形参进行初始化的,其形式可以是常量、变量、表达式、函数调用等。但其值必须是确定的。实参与形参个数相等,类型一致,按顺序一一对应,如果实参表列包含多个实参,则各参数间用逗号隔开。如上面例中的 cmax(a,b); 函数调用的方式:按函数在程序中出现的
6、位置来分,可以有以下三种: 函数语句:把函数调用作为一个语句。通常用于不带返回值的函数。 例:print(5); 函数表达式:函数出现在一个表达式中。要求函数带回一个确定的值以参加表达式的运算。 例:c = max(a,2*b); 函数参数:函数调用作为另一个函数的实参。 例:d = max( max(a,b), c );,9,5.2 函数调用,函数调用过程 设程序p1具有三个函数,main(),f1(),f2() ,其调用关系是main函数调用f1函数,f1函数调用f2函数,程序如下:,p1程序的执行过程如下图所示 :,分析程序的执行过程,可以发现以下特征: (1)main最先执行、最后结束
7、,f2最后执行却最先结束,正所谓“先进后出”; (2)每一函数在调用另一函数时必须暂时中断自身的执行; (3)每一个被调用的函数执行结束后,必须能返回主调函数调用处的下一执行点,,10,5.2 函数调用,例:编写程序,求5!+7!+9!,#include int f1(int n) int s=1;for(int I=1;I=n;I+)s=s*I;return s; void main() int m;m=f1(5)+f1(7)+f1(9);cout“5!+7!+9!=“mendl; ,11,5.3 函数原型,(三) 函数原型 如果被调函数定义出现在主调函数之后,则要先声明被调用函数,这种声明
8、称为函数原型。被调用函数定义出现在主调函数之前则不用声明。函数原型:是对已经定义的函数的概要描述,告诉编译系统函数类型、参数个数及类型,以便检验。 函数原型是一条程序语句,它由函数头部和分号组成, 形式为: 函数类型 函数名(形参列表); 应当保证函数原型与函数头部写法上的一致,即函数类型、函数名、参数个数、参数类型和参数顺序必须相同。 函数原型说明有两种形式: a、直接使用函数定义的头部,并在后面加上一个分号;例:函数max的函数原型为:float max(float x,float y); b、在函数原型说明中省略参数列表中的形参变量名。例:函数max的函数原型为:float max(fl
9、oat ,float );,12,5.3 函数原型,#include float max(float x,float y); void main( ) c = max(a,b); float max(float x,float y) 函数声明和函数定义的区别:这是两个完全不同的概念,有本质区别。 a、函数的定义是编写一段程序,有函数体;而函数的声明只是一个说明,不含具体的执行动作。 b、在程序中,函数的定义只能有一次,而函数的声明可以有多次。,函数原型,函数调用,函数定义,13,5.3 函数原型,上机作业 1、编写一个函数,判断一个整数是否是质数,在主函数中进行输入和输出。分析:m是质数的条件
10、是不能被2,3,m-1整除。 2、给定某个年、月、日的值,例如,2005年7月11日,计算出这一天属于该年的第几天,要求写出计算闰年的函数和计算日期的函数。,P149 17,14,5.4 函数参数,(四)函数参数 函数使用时,主调函数和被调用函数之间经常有数据传递关系。这种数据传递是通过函数参数的传递来实现的。 (1)形参与实参 形式参数:定义函数时函数名后面括号中的变量名; 实际参数:调用函数时函数名后面括号中的表达式;说明: 实参必须有确定的值; 形参必须指定类型; 形参与实参类型一致,个数相同; 形参在函数被调用前不占内存;函数调用时为形参分配内存;调用结束,内存释放。,15,5.4 函
11、数参数,(2)参数的传递 C+有两种向函数传递参数的办法:传值、传地址。 1)传值 方式:函数调用时,为形参分配单元,并将实参的值复制到形参中;调用结束,形参单元被释放,实参单元仍保留并维持原值。 特点: 形参和实参都是简单变量; 形参与实参占用不同的内存单元; 单向传递,实参传给形参,不能由形参传回来给实参; 形参改变,实参不会改变。,16,5.4 函数参数,关于值传递参数应当注意下面几个问题:1)形参在没有被调用时,不占用存储空间。只有在发生函数调用时,才为形参开辟存储空间,并传递相应的值。当函数结束后形参释放其所占用的存储空间,函数返回值。 2)C+函数参数的求值顺序是自右至左的,即 C
12、+函数中实参的值是从右到左确定的。例:#include int some_fun(int a,int b) return a+b; void main( ) int x,y;x=2; y=3;coutsome_fun(+x , x+y)endl;x=2; y=3;coutsome_fun(x+y , +x)endl; ,17,5.4 函数参数,2)传地址(形参为指针变量) 方式:函数调用时,将数据的存储地址作为参数传递给形参。 特点: 形参是指针变量,实参是地址值; 形参与实参占用同样的存储单元; “双向”传递; 形参改变,实参也改变。,例: void f1(int *p1,int *p2)
13、*p1=10;*p2=20; Void main() int a=2,b=3;f1( ,18,5.4 函数参数,例 交换两个数,#include void swap(int *,int *); void main() int a=5,b=9;cout“a=“a“,b=“bendl;swap( /间接访问 ,指针,地址,19,5.4 函数参数,例 试图用传值方式交换两个数,#include void swap(int,int); void main() int a=7,b=11;cout“a=“a“,b=“bendl;cout“交换:“endl;swap(a,b);cout“a=“a“,b=“b
14、endl; void swap(int x,int y) int temp;temp=x; x=y; y=temp; ,20,5.4 函数参数,3)传地址(形参为引用) 方式:函数调用时,形参被初始化为实参的引用,即形参是实参的别名,对形参的操作就是对实参的操作。 特点: 形参是引用,实参是变量; 形参与实参占用同样的存储单元; “双向”传递; 调用形式和值参数相同; 形参改变,实参也改变。,例: void f1(int ,21,5.4 函数参数,例 交换两个数,#include void swap(int ,引用,变量,22,5.4 函数参数,在C+中,当函数参数需要传递地址时,建议使用引用
15、来代替指针(指针方式属于C语言风格),因为引用比指针更加直观。 在下面情况中,使用引用参数显得十分有用: 要从函数中返回多个值; 通过函数调用要改动实参值; 当参数是大数据类型时,如类,使用引用参数将节省参数传递时的时间和空间。 使用引用(或指针)可能会使得不该修改的实参变量由于不注意而在函数调用时被修改。为保护实参不被修改,使用常量引用参数,方法是使用关键字const来修饰引用参数。 例:int f1(const int ,23,5.4 函数参数,上机作业: 1、编写一个程序,用下面指定的两个函数将main中定义的变量n乘以3,结果有什么不同。这两个函数如下所示: (1)函数F1按值传递n的
16、副本,将副本乘以3,然后返回新值。 (2)函数F2通过引用参数传递n,通过别名将n乘以3。 2、编写一个函数计算矩形的面积和周长,要求在main函数中输入输出。,24,5.4 函数参数,(3)数组作为函数参数数组可以作为函数参数,在定义函数时称为数组形参,在调用函数时,称为数组实参。C+中数组不采用传值方式,这是因为传递数组的每一个值将会占用大量存储空间,因此将数组作为参数传给函数时,系统只是把数组的地址传给函数。这样对数组形参的操作就等于对数组实参的操作。 数组形参的描述格式 在函数定义中,数组形参的描述格式如下: 下标说明 例如:f1(int array ) 形式参数array是一 个一维
17、整型数组。 数组形参的下标说明可以省略。 数组实参的描述格式 在调用含有数组形参的函数时,对应于数组形参,其实参是数组名而不能是数组元素。 例如: f1(int c,int array) f2( ) int array10,a; f1(a,array); 以数组名作为参数传递实际上是传递数组首地址,所以被调函数对数组的修改可以返回主调函数 。,25,5.4 函数参数,例:编写一个程序,用来输入10名学生的C+成绩,存放到数组a中,并计算平均成绩。 思路: 我们可以将该程序的功能分解成:输入成绩、计算、输出成绩三个子功能,分别由input、avge和output三个函数实现。 三个函数之间要传递
18、成绩数据,该数据存放在a数组中,也即a数组和数组大小作为函数参数在main、input、avge、output之间来回传递。,因为数组名传递的是数组地址,没有数组大小的信息,所以另外要传递一个代表数组大小的参数。,void input(float a,int x) for(int I=0;IaI; ,void output(float a,int x) for(int I=0;Ix;I+)coutaI; ,float avge(float a,int x) float sum=0;for(int I=0;Ix;I+)sum+=aI;return sum/x; ,void main() floa
19、t array10;input(array,10);output(array,10);cout“平均分=”;coutavge(array,10); ,26,5.4 函数参数,(4)指向数组的指针作函数的参数 由于在传递数组时,系统将数组的首地址传给形参,所以形参可以是数组形参(这样数组形参和数组实参的首地址相同),也可以是指针变量形参(这样指针变量指向的是数组实参的首地址,对指针变量的间接访问就是对数组实参的访问)。 形参格式:void f1(int *p,int n) 实参格式:int a10; f1(a,10); /用数组名a作为实参调用f1函数。或:int a10,*q=a; /定义指向
20、数组a的指针变量q。f1(q,10); /以指向数组的指针q作实参调用f1函数。,练习:使用指向数组的指针变量作函数的参数修改前一个例子。,27,5.4 函数参数,(5)指向字符串的指针作函数的参数 字符串指针作函数的参数,与前面介绍的数组指针作函数参数没有本质的区别,函数间传递的都是地址值,所不同的仅是指针指向对象的类型不同而已。 例:用字符串指针作函数参数,将输入的一个字符串复制到另一个字符串中。,/将s1指向的字符串复制到s2中. void copy_s(char *s1,char *s2) while(*s2=*s1)!=0)s1+;s2+; ,void main() char a20
21、,b30;cina; /* 输入原始字符串 */copy_s(a,b); /* 将字符串a复制到字符串b中 */coutbendl; /* 输出字符串 */ ,void main() char a20,b30,*pa=a,*pb=b; /* 定义指向字符串的指针*/ cinpa; /* 从键盘输入一个字符串,并用pa指针指向这个字符串 */ copy_s(pa,pb); /* 用字符串指针作实参调用函数 */ coutpb; /* 输出pb指向的字符串 */ ,28,5.4 函数参数,练习:编写一个函数,实现字符串的连接,即使一个字符串s2接到另一个字符串s1的后面,函数返回s1的值。,#in
22、clude using namespace std; void ff(char *s1,char *s2) while (*s1!=0)s1+;while(*s2!=0)*s1+=*s2+;*s1=0; ,int main() char a50,b10;cinab;ff(b,b);coutaendl;return 0; ,29,5.4 函数参数,上机作业: 1、用数组和指针变量作为形参分别编写一个函数,该函数查找整型数组中最大值和最小值。,30,5.4 函数参数,(6)默认参数 通常,调用函数时,要为函数的每个形参给定对应的实参。 在C+中,可以为参数指定默认值,当在函数调用时没有指定与形参相
23、对应的实参时就自动使用默认值。 默认参数的声明默认参数通常在函数名第一次出现在程序中的时候: 如有函数原型,则在函数原型中指定默认值。在函数定义中不能再指定默认值。void point(int=3,int=4); void point(int x=3,int y=4) /错:定义不能再给出默认值 cout xendl; cout yendl; 没有函数原型,则在函数定义中指定默认值。void point(int x=3,int y=4) /定义中给出默认值 cout xendl; cout yendl; ,31,5.4 函数参数,默认参数的顺序规定 如果一个函数中有多个默认参数,则形参分布中,
24、默认参数应从右至左逐渐定义。当调用函数时,实参与形参按从左到右的顺序进行匹配。void func(int a=1,int b,int c=3, int d=4); /错 void func(int a, int b=2,int c=3,int d=4); /对 对于第2个函数声明,其调用的方法规定为:func(10,15,20,30); /对:调用时给出所有实参 func(2,12); /对:参数c和d默认 func(2,15,20); /对,参数d默认func(); /错:参数a没有默认值,void func(int a,int b,int c,int d) couta“,”b“,”c“,”
25、dendl; ,32,5.4 函数参数,上机作业:1、编写一个程序,内有一个带有默认参数的函数,其含有两个参数:一个是字符类型,另一个是整数类型。这个函数能够输出一行重复的字符,重复的字符和次数由传送给函数的字符和整数决定。例如:传送给函数#和20,则在一行中输出20个#,如果整数没有被传递,则输出10个&,如果字符和整数都没有被传递,则在一行中输出10个$.,33,5.5 函数重载,(五)函数重载函数重载是指同一个函数名对应着多个不同函数的实现。所谓“不同”是指这些函数的形参表必须互不相同,或者是形参的个数不同,或者是形参的类型不同,或者是两者都不相同。函数的重载就类似于多义字(词),同一个
26、字(词)在不同的条件下,可能有不同的意思。 例:,34,5.5 函数重载,重载函数的形参必须不同: 个数不同或类型不同。 编译程序将根据实参和形参的类型及个数的最佳匹配来选择调用哪一个函数。 不要将不同功能的函数定义为重载函数,以免出现调用结果的误解、混淆。这样不好:,35,5.5 函数重载,例:求两个数中的最大值。,#include int max(int a,int b); double max(double a,double b); char max(char a,char b); char * max(char *a,char *b); void main() coutb)return
27、 a;elsereturn b; ,double max(double a,double b) if (ab)return a;elsereturn b; char max(char a,char b) if (ab)return a;elsereturn b; char * max(char *a,char *b) if (strcmp(a,b)0)return a;elsereturn b; ,36,5.5 函数重载,练习:求两个整数之和的函数与求三个整数之和的函数重载。,37,5.6 内联函数,(六)内联函数内联函数就是在程序编译时,编译器将程序中出现的内联函数的调用表达式用内联函数的函
28、数体来代替。引入内联函数的目的是为了提高程序中函数调用的效率。1、内联函数的定义 定义内联函数只要在函数定义的头前面加上关键字inline即可。其它与函数定义相同。格式: inline () 例如: inline int add_int(int x,int y,int z) return x+y+z; ,38,5.6 内联函数,注意: 内联函数体内不能有循环语句和switch语句。 内联函数的定义必须出现在内联函数第一次被调用之前。 内联函数不能是递归函数。 语句数尽可能少,一般不超过5行。 2、内联函数的声明 与其它函数一样,内联函数必须先声明后使用,如果要声明一个内联函数原型,则必须加上声
29、明关键字inline ,此时函数定义头部的inline可省略。例如: inline int add_int(int x,int y,int z);/函数原型 例:#include int power_int(int x); /函数原型没有inline ,不是内联函数。 void main( ) for (int p,int i=1;i=10;i+) p=power_int(i); couti*i=pendl; ,inline int power_int(int x) return (x)*(x); ,39,5.6 内联函数,上机作业: 1、设计两个求面积的函数:area(); /求圆面积,需传
30、递一个参数area(); /求矩形面积,需传递两个参数 2、编写一个求两个双精度浮点数中最大值的函数,要求将该函数定义为内联函数。,P149 第20题,练习:输入一个整数m,判断它能否被3整除,要求利用内联函数来实现该程序。,40,5.7 递归函数,(七)函数的嵌套调用和递归调用 (1)函数的嵌套调用 函数定义都是互相平行、独立的。不能嵌套定义函数,但可以嵌套调用函数,即在调用一个函数的过程中,又调用另一个函数。 例1已知组合数 ,求对于任意m、n时的值。 用两个自定义函数求解问题:(1) 定义fac()函数求一个数的阶乘; (2) 定义利用fac()函数求组合数的函数cmn()。,/*定义f
31、ac()函数求k的阶乘*/ float fac(int k) int i;float f;for(i=1,f=1;i=k;i+)f*=i;return(f); ,/*定义求组合数的函数cmn()*/ float cmn(int m,int n) float res; /*调用fac()函数*/res=fac(m)/(fac(n)*fac(m-n); return(res); ,void main() int m,n;float t;cout mn; /*调用函数cmn()*/t=cmn(m,n); cout“C(“m“,” n“)=”tendl; ,41,5.7 递归函数,练习:输入两个整数,
32、求两个数的平方和。,#include int fun1(int x,int y); int fun2(int m); void main(void) int a,b;cinab;cout“a、b的平方和:“fun1(a,b)endl; int fun1(int x,int y) return (fun2(x)+fun2(y); int fun2(int m) return (m*m); ,42,5.7 递归函数,(2)函数的递归调用函数的递归调用是指在函数体内部直接或间接地自己调用自己,即函数嵌套调用的是函数本身。 递归调用的形式:直接递归调用和间接递归调用 直接递归调用:即在函数中出现调用函
33、数本身。例如: int fun1( ) / 函数其他部分 z=fun1( ); / 直接调用自身 / 函数其他部分 在函数fun1( )中,又调用了fun1( )函数,这是直接调用。 间接递归调用:即在一个函数中调用了其他函数,而在该其他函数中又调用了本函数。,函数fun2( )中调用了fun3( ),而fun3( )中又调用了fun2( ),这是间接递归调用。,int fun3( ) y=fun2( ); ,int fun2( ) x=fun3( ); ,43,5.7 递归函数,递归的条件 运用递归调用技术必须满足以下三个基本条件: 1)可以把一个问题转化为一个新的问题,新问题的解决方法必须
34、与原问题的解决方法相同,只是所处理的对象有所不同,但所处理的对象其属性的变化必须是有规律的。 2)可以将问题的解决方法描述成相对独立的功能(过程化),以便于用函数来实现。 3)递归函数中必须有确定的结束递归的条件,并且应该遵循先测试后调用的原则。如果函数中没有确定的结束递归的条件,将会造成无限递归。如果不遵循先测试后调用的原则,那么递归结束条件形同虚设。,设n=5,求5!的过程如下:,#include long fac(int n) if(n=1)return(1);else return n*fac(n-1); void main() long y;int n;coutn;y=fac(n);
35、cout n “!=“ y endl; ,44,5.7 递归函数,例:用递归方法求Fibonacci数列的前20列。 Fibonacci数列是指前两项为1,从第三项起每一项为前两项数据之和,如1,1,2,3,5,8,13,45,5.7 递归函数,例: 汉诺塔(Hanoi)问题,A,n个盘子,问题: 将A塔上n个盘子移至C(借助于B)。 移动时,保证三个塔始终是大盘在下,小盘在上。,46,5.7 递归函数,必须用递归方式解决,1) 先将A塔n 1个盘子借助于C移至B上,2) 将A上剩下的一个移至C上.,3) 将B上n 1个盘子借助于A移至C上.,可以看到:1)、3)为同一问题,都为n 1个盘子借
36、助于一个空塔移至另一塔上。,47,5.7 递归函数,#include void move (char g,char p) cout”pendl; void hanoi (int n, char one, char two, char three) /*将n个盘从one借助two,移到three*/ if (n= =1) move (one, three);else hanoi (n1, one, three, two); move (one, three); /*函数调用*/ hanoi (n1, two, one, three); ,void main ( ) int m;coutm;cou
37、t“ 移动“m”个盘子的步骤:n “;hanoi (m, A, B, C); ,在程序中有两个函数: move (g, p) 表示从g 塔移一个盘子至p塔。 hanoi(n, one, two, three) 表示n个盘子从one塔借助于two塔(空)移至three塔,调用时塔用字符常量A , B , C 表示。,48,5.7 递归函数,练习: 1、求第n个人的年龄。每个人比前者大2岁,第一个人10岁。 2、在主程序中提示输入n,编写函数用递归的方法求1+2+n的和。,49,5.7 递归函数,上机作业:,P149 第18、19题,50,5.8 变量作用域与生存周期,1、函数原形的作用域 函数原
38、型中的参数,其作用域始于 “(“,结束于“)“。 例如,设有下列原型声明: double Area(double radius);,radius 的作用域仅在于此,不能用于程序正文其它地方,因而可有可无。,(八)作用域和存储类型 作用域讨论的是标识符的有效范围;可见性是标识符是否可以引用的问题。 一、作用域作用域是一个标识符在程序中的有效范围。有函数原型作用域、块作用域、函数作用域和文件作用域。,51,5.8 变量作用域与生存周期,2、块作用域(局部作用域),在一个复合语句内声明的标识符具有块作用域。其作用域自声明处起,一直到块结束的大括号为止,限于块中,例如: void fun(int a)
39、 int b;cinb;if (b0)int c; ,a的作用域,形参属于块作用域。,52,5.8 变量作用域与生存周期,3、文件作用域,在所有的函数和类之外定义的标识符具有文件级作用域。其作用域自声明处起,一直到源文件的结束为止。例如:int n=0; void fun(int a) int b;cinb;n=n+b+a;coutnendl; void main() ,n的作用域,b的作用域,53,5.8 变量作用域与生存周期,二、局部变量和全局变量 1、局部变量 在一个函数内部定义的变量称为局部变量,具有局部作用域(块作用域),它只在本函数范围内有效。函数的形参变量和函数体内定义的变量都是
40、局部变量。,void main( ) int x ; /x为定义在main函数中的局部变量其作用域为main函数内 int y ; /y为定义在块内的局部变量,其作用域为块内 ,54,5.8 变量作用域与生存周期,例:局部变量的使用,#include int fun1(int a,int b) int c; a=a+1;b=b+1;c=a+b; return c; ,void main( ) int a,b,c;coutab;c=a+b;coutfun1(a,b)endl;cout“a+b=“cendl; ,不同函数如果使用相同的参数或变量,它们仅在其所在函数体内有效,互不影响。,55,5.8
41、 变量作用域与生存周期,2、全局变量(外部变量) 在函数和类之外定义的变量称为全局变量。具有文件作用域。全局变量可以为本文件中其他函数所共用。它的有效范围为从定义变量的位置开始到本源文件结束。,#include int p=1,q=5; /*全局变量*/ float f1(int a) /*定义函数f1/ int b,c; char c1,c2; /* 全局变量 */ char f2 (int x, int y)/*定义函数f2*/ int i,j; void main ( ) /*主函数*/ int m,n; ,全局变量p、q的作用范围,全局变量c1、c2的作用范围,56,5.8 变量作用域
42、与生存周期,3、注意事项 (1)局部变量在定义时没有初始化,其初始值是不可预料的。全局变量定义时如果没有初始化,则编译程序会自动对全局变量初始化为0或空字符。例:int a; void main() cout int a=10; main( ) int a,b; a=5; cout “a=“ a; ,57,5.8 变量作用域与生存周期,例:局部变量和全局变量的应用。,利用全局变量可以减少参数的数量和数据传递的时间,但建议:限制使用全局变量,因为: 全局变量在程序全部执行过程中占用存储单元; 降低了函数的可理解性、可靠性,可移植性。,#include float Max=0,Min=0; voi
43、d max_min(float array,int n) int i; Max=Min=array0;for(i=1;iMax) Max=arrayi;if(arrayiMin) Min=arrayi; ,void main() float score10;int i; for(i=0;iscorei);max_min(score,10); cout“max=“Maxendl; cout“min=“Minendl; ,58,5.8 变量作用域与生存周期,练习:写出程序的输出结果。 #include int i; /文件作用域 int main() i=5; int i; /块作用域i=7;co
44、ut“i=“iendl; cout“i=“i; return 0; ,59,5.8 变量作用域与生存周期,三、变量的存储类型 1、 程序的内存区域 程序执行必须先调入到内存中,内存中存放了程序的代码和程序中用到的数据。程序在内存中执行时分别占用四部分空间。 (1)代码区:存放程序的代码。(2)全局数据区:存放程序的全局数据和静态数据。如全局变量、静态变量、常量。 (3)堆区:动态数据分配空间。由new、delete动态实现。 (4)栈区:局部变量、函数参数、返回数据、返回地址。 变量主要存放在全局数据区、堆区、栈区。另外还可以分配在寄存器中。每一个变量都有两个属性:数据类型和存储类型。 数据类
45、型(如整型、字符型等)规定了变量能存储哪一类数据。 存储类型则规定了变量存放在哪个内存区域,怎样存储。,动态存储区,静态存储区,60,5.8 变量作用域与生存周期,2、存储类型 +中变量的存储类型有四种: auto(自动型)-函数内部的局部变量。 register(寄存器型)-变量存储在硬件寄存器中。 static(静态型)-静态存储分配,又分为内部和外部静态。 extern(全局型)-全局变量(用于外部变量说明) 变量定义格式: 存储类型 数据类型 变量名,如: int sum;auto int a,b,c;register int i;static float x,y;,61,5.8 变量
46、作用域与生存周期,(1)自动变量(auto) 只有函数的局部变量才能定义为auto型,auto型变量在函数被调用时系统会给它们分配存储空间,数据存储在栈区中,在函数调用结束时就自动释放这些存储空间。由一次调用到下一次调用之间不保存值。例:auto int a3;int a3; 等价。关键字auto可以省略。 int f(int a) /* 定义f函数,a为形参 */ auto int c3;/* 定义c为自动变量 */c=c+1;coutacendl; 形参a是自动变量,c是自动变量,对c赋初值3。执行完f函数后,自动释放a,c 所占的存储单元。,62,5.8 变量作用域与生存周期,(2)寄存
47、器变量(register) 只有局部变量和函数参数可指定为寄存器变量,它的作用域与生存期与自动变量完全相同,但其存储空间分配在CUP的寄存器而不是分配在内存,这样可以提高存取速度。 当某些变量使用频率比较高时可以定义为寄存器变量。,63,5.8 变量作用域与生存周期,(3)外部变量(全局变量) 在函数和类的外部定义的变量即为外部变量。存放在全局数据区。 外部变量的作用域是从定义变量的位置开始到本源文件结束。 在同一文件中,如果前面的函数要引用在其后面定义的外部变量时,要用extern加以说明。 如果某个工程有几个源文件,外部变量只需在某个文件上定义一次,其他文件若要引用此变量时,也要用exte
48、rn加以说明(外部变量定义时不必加extern关键字)。,#include extern int n; void main() coutnendl; int n=0; void f1() n=n+1; ,64,5.8 变量作用域与生存周期,(4)静态变量(static) 有:静态局部变量(静态内部变量)和静态全局变量(静态外部变量) 静态局部变量:在局部变量前加上“static”关键字就成为静态局部变量。 静态局部变量仍是局部变量,其作用域在定义它的函数内。只能被定义它的函数使用。 静态局部变量存放在内存的全局数据区,静态局部变量一经定义不会再次分配存储空间,也不会自行消失,直到程序运行结束,这一点与全局变量相同。 如果定义静态局部变量时不赋初值,则编译时自动赋0或空字符。这一点又与全局变量相同。,