




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、第第3章章 函数和编译预处理函数和编译预处理3.1 函数概述函数概述 3.2函数的定义和调用函数的定义和调用 3.3 函数的参数传递函数的参数传递 3.4 函数的嵌套调用和递归调用函数的嵌套调用和递归调用 3.5 内置函数内置函数 3.6 变量和函数的属性变量和函数的属性 3.7 编译预处理编译预处理 3.1 概述概述一个较大的程序不可能完全由一个人从头至尾地完成,一个较大的程序不可能完全由一个人从头至尾地完成,更不可能把所有的内容都放在一个主函数中。为了便更不可能把所有的内容都放在一个主函数中。为了便于规划、组织、编程和调试,一般的做法是把一个大于规划、组织、编程和调试,一般的做法是把一个大
2、的程序划分为若干个程序模块的程序划分为若干个程序模块( (即程序文件即程序文件) ),每一个,每一个模块实现一部分功能。不同的程序模块可以由不同的模块实现一部分功能。不同的程序模块可以由不同的人来完成。在程序进行编译时,以程序模块为编译单人来完成。在程序进行编译时,以程序模块为编译单位,即分别对每一个编译单位进行编译。如果发现错位,即分别对每一个编译单位进行编译。如果发现错误,可以在本程序模块范围内查错并改正。在分别通误,可以在本程序模块范围内查错并改正。在分别通过编译后,才进行连接,把各模块的目标文件以及系过编译后,才进行连接,把各模块的目标文件以及系统文件连接在一起形成可执行文件。统文件连
3、接在一起形成可执行文件。在一个程序文件中可以包含若干个函数。无论把一个程序划分为多少个程序模块,只能有一个main函数。程序总是从main函数开始执行的。在程序运行过程中,由主函数调用其他函数,其他函数也可以互相调用。在C语言中没有类和对象,在程序模块中直接定义函数。可以认为,一个C程序是由若干个函数组成的,C语言被认为是面向函数的语言。C+面向过程的程序设计沿用了C语言使用函数的方法。在C+面向对象的程序设计中,主函数以外的函数大多是被封装在类中的。主函数或其他函数可以通过类对象调用类中的函数。无论是C还是C+,程序中的各项操作基本上都是由函数来实现的,程序编写者要根据需要编写一个个函数,每
4、个函数用来实现某一功能。因此,读者必须掌握函数的概念以及学会设计和使用函数。 “函数”这个名词是从英文function翻译过来的,其实function的原意是“功能”。顾名思义,一个函数就是一个功能。在实际应用的程序中,主函数写得很简单,它的作用就是调用各个函数,程序各部分的功能全部都是由各函数实现的。主函数相当于总调度,调动各函数依次实现各项功能。开发商和软件开发人员将一些常用的功能模块编写成函数,放在函数库中供公共选用。程序开发人员要善于利用库函数,以减少重复编写程序段的工作量。图3.是一个程序中函数调用的示意图。 图3.main()func1()func2()func3()func5()
5、func4()例31 在主函数中调用其他函数。#include /*ex3_1.cpp*int calc_sum(int n) /定义定义calc_sum()函数函数 int k,s; s=0; for (k=1;k=n;k+) s=s+k; return s; void print_word(void) /定义定义print_word()函数函数 coutHello,C+!n; if (n1) coutthe sum is: calc_sum(n)endl; /调用调用calc_sum()函数函数 print_word(); /调用调用rint_word()函数函数 若用户从键盘输入的数是4
6、, 运行情况如下:the sum is:10Hello,C+! 从用户使用的角度看,函数有两种:(1) 系统函数,即库函数。这是由编译系统提供的,用户不必自己定义这些函数,可以直接使用它们。(2) 用户自己定义的函数。用以解决用户的专门需要。从函数的形式看,函数分两类:(1) 无参函数。调用函数时不必给出参数。(2) 有参函数。在调用函数时,要给出参数。在主调函数和被调用函数之间有数据传递。3.2 函数的定义和调用 3.2.1 定义无参函数的一般形式定义函数的一般形式如下:类型标识符 函数名(形式参数列表) 声明语句执行语句【例3.1】的print_word函数是无参函数,它的定义中首句也可以
7、写成:void print_word( ) /定义print_word()函数的首部; 或:void print_word(void) /定义print_word()函数的首部【例3.1】的calc_sum函数是有参函数,不过它只有一个参数,参数的类型是整数类型。 int calc_sum(int n) /定义calc_sum()函数 C+要求在定义函数时必须指定函数的类型。下面的函数f则有两个参数,第一个是整数类型,第二个是单精度实数类型,而函数的返回值是双精度实数类型:double f(int x, float y) /定义f( )函数的首部 (1)对库函数的声明其实,对库函数的声明语句已
8、经写在有关包含文件中了,因此只要在程序文件头用include语句将这些包含文件包含到本程序中来,就完成了对库函数的声明。 (2)对自定义函数的声明必须在调用某自定义函数的语句之前写上如下声明语句:函数类型关键字 函数名(参数1类型,参数1名称,参数2类型,参数2名称); 3.2.2 函数的声明(2)对自定义函数的声明 (续)也可以在声明语句中略去参数的名称,或写一个任意名称,这叫做函数原型(function prototype),即声明函数原型。函数原型有下列两种表示形式:函数类型关键字 函数名(参数1类型,参数2类型);函数类型关键字 函数名(参数1类型,标识符1,参数2类型,标识符2);C
9、+中的函数原型说明了函数的类型、函数名、函数各形式参数类型。使用函数原型是C和C+的一个重要特点。 3.2.2 函数的声明【例例3.2】 对被调函数做声明的示例。对被调函数做声明的示例。#include /*ex3_2.cpp*void main() float add(float x,float y); /对对add函数作声明函数作声明 float subtract(float,float); /对对subtract函数作声明,用函数原型函数作声明,用函数原型 double multiply(float p,float q); /对对multiply函数作声明,参数名任意函数作声明,参数名任
10、意 float a,b,c1,c2; double c3; cout ab; c1=add(a,b); c2=subtract(a,b); c3=multiply(a,b); cout c1,c2,c3endl; float add(float x,float y) /定义add函数 float z; z=x+y; return (z);float subtract(float x,float y) /定义subtract函数 float z; z=x-y; return (z);double multiply(float x,float y) /定义multiply函数 double z;
11、z=x*y; return (z);运行情况如下:please input a,b:4.25 2.00 6.25,2.25,8.5 说明:(1)对函数的定义和函数声明是两回事,不要混淆。 (2)之所以函数原型中可以省略形式参数的名称,是因为形式参数的名称是无关紧要的,且在调用前形参并不存在。 (3)函数声明语句的位置。函数声明语句可以放在主调函数中,也可放在函数外面,只要出现在调用语句之前即声明有效。 3.2.3 函数的返回值(1) 函数的返回值是通过函数中的return语句获得的。return语句将被调用函数中的一个确定值带回主调函数中去。return语句后面的括号可以要,也可以不要。ret
12、urn后面的值可以是一个表达式。(2) 函数值的类型。既然函数有返回值,这个值当然应属于某一个确定的类型,应当在定义函数时指定函数值的类型。(3) 如果函数值的类型和return语句中表达式的值不一致,则以函数类型为准,即函数类型决定返回值的类型。对数值型数据,可以自动进行类型转换。第(1)种形式是基本的形式。为了便于阅读程序,也允许在函数原型中加上参数名,就成了第(2)种形式。但编译系统并不检查参数名。因此参数名是什么都无所谓。上面程序中的声明也可以写成float add(float a,float b); /参数名不用x、y,而用a、b 效果完全相同。应当保证函数原型与函数首部写法上的一致
13、,即函数类型、函数名、参数个数、参数类型和参数顺序必须相同。在函数调用时函数名、实参类型和实参个数应与函数原型一致。说明: (1) 前面已说明,如果被调用函数的定义出现在主调函数之前,可以不必加以声明。因为编译系统已经事先知道了已定义的函数类型,会根据函数首部提供的信息对函数的调用作正确性检查。有经验的程序编制人员一般都把main函数写在最前面,这样对整个程序的结构和作用一目了然,统览全局,然后再具体了解各函数的细节。此外,用函数原型来声明函数,还能减少编写程序时可能出现的错误。由于函数声明的位置与函数调用语句的位置比较近,因此在写程序时便于就近参照函数原型来书写函数调用,不易出错。所以应养成
14、对所有用到的函数作声明的习惯。这是保证程序正确性和可读性的重要环节。(2) 函数声明的位置可以在调用函数所在的函数中,也可以在函数之外。如果函数声明放在函数的外部,在所有函数定义之前,则在各个主调函数中不必对所调用的函数再作声明。例如: char letter(char,char); /本行和以下两行函数声明在所有函数之前且在函数外部float f(float,float); /因而作用域是整个文件因而作用域是整个文件 int i(float, float); int main( ) /在在main函数中不必对它所调用的函数作声明函数中不必对它所调用的函数作声明char letter(char
15、 c1,char c2) /定义定义letter函数函数float f(float x,float y) /定义定义f函数函数 int i(float j,float k) /定义定义i函数函数如果一个函数被多个函数所调用,用这种方法比较好,不必在每个主调函数中重复声明。函数调用的一般形式:函数名(实际参数列表) 如果是调用无参函数,则“实参表列”可以没有,但括号不能省略。如果实参表列包含多个实参,则各参数间用逗号隔开。实参与形参的个数应相等,类型应匹配(相同或赋值兼容)。实参与形参按顺序对应,一对一地传递数据。但应说明,如果实参表列包括多个实参,对实参求值的顺序并不是确定的。3.2.4函数的
16、调用按函数在语句中的作用来分,可以有以下3种函数调用方式:. 函数语句把函数调用单独作为一个语句,并不要求函数带回一个值,只是要求函数完成一定的操作。如【例3.1】中的print_word()函数调用语句。 2. 函数表达式函数出现在一个表达式中,这时要求函数带回一个确定的值以参加表达式的运算。如c=2*max(a,b);3. 函数参数函数调用作为一个函数的实参。如m=max(a,max(b,c); /max(b,c)是函数调用,其值作为外层max函数调用的一个实参函数调用的方式3.3 函数的参数传递 形参:在定义函数时函数名后面括号中的变量名称为形式参数(formal parameter),
17、简称形参。形参是无内存单元(因而不存在)的任何合法标识符。实参:在调用一个函数时,调用语句的函数名后面括号中的参数称为实际参数(actual parameter),简称实参。实参是实际存在(因而有特定值)的常量、变量或表达式。 【例例3.3】 形参和实参及其数据传递。形参和实参及其数据传递。#include /*ex3_3.cpp* using namespace std;float volume(float r,float h) /定义有参函数定义有参函数volume求圆柱体体积,求圆柱体体积,r和和h是形参是形参 float v;v=3.14*r*r*h; return (v);void
18、main()float a,b,c;cout a b;c=volume(a,b); /调用函数调用函数volume,a和和b是实参。函数值赋给变量是实参。函数值赋给变量ccout The volume is cendl; 运行情况如下:please input two float nukbers:2.0 5.0 The volume is 62.8有关形参与实参的说明:(1) 在定义函数时指定的形参,在未出现函数调用时,它们并不占内存中的存储单元,因此称它们是形式参数或虚拟参数,表示它们并不是实际存在的数据,只有在发生函数调用时,函数max中的形参才被分配内存单元,以便接收从实参传来的数据。在
19、调用结束后,形参所占的内存单元也被释放。(2) 实参可以是常量、变量或表达式,如max(3, a+b);但要求a和b有确定的值。以便在调用函数时将实参的值赋给形参。(3)在定义函数时,必须在函数首部指定形参的类型,至于形参使用何名字可随意。 (4)实参与形参的类型应相同或赋值兼容。两种参数类型完全一致无疑是完全合法、正确的。如果实参为整型而形参为实型,或者相反,则按不同类型数值的赋值规则进行转换,原则上不出现语法错误,但结果可能带来某些非期望的误差。例如实参a的值是3.5,而被调函数的形参x为整型,则调用该函数时会将3.5转化为整数3,然后送到形参x,故x得到的是3,由3计算出的函数值与人们期
20、望由3.5计算出的函数值一般是有差别的。但字符型与整型可以互相通用。 3.3.2 参数的值传递 值传递参数的实现是系统将实参拷贝一个副本给形参,拷贝后两者就断开关系。在被调函数中,形参可以被改变,但这只影响副本中的形参值,而不影响调用函数的实参值。所以这类函数有对原始数据保护的作用。换一句话说,这种参数传递机制是单向影响,即只能由实参将值传给形参(实参影响形参);而形参在函数中的值如果发生修改,不会反过来影响与之对应的实参。 【例3.4】 参数值传递的演示。#include /*ex3_4.cpp* using namespace std;int max(int x,int y) /定义有参函
21、数max求两数最大值,x和y是形参 float m;coutThe initial x,y:x,yy?x:y;x=2*x;y=y+1;coutThe new x,y:x,yendl;return (m);void main()float a,b,c;cout a b;c=max(a,b); /调用函数max,a和b是实参,函数值赋给变量c。cout The maxinum is: cendl;cout After calling fuction,a,b:a,bendl;运行情况如下:please input two integer numbers:3 8 The initial x,y:3,8
22、The new x,y:6 9The maxinum is:8After calling fuction,a,b:3,83.3.3 参数的地址传递除了3.3.2小节介绍的值传递参数方式外,函数调用还有一种特殊的值传递形式,即传递的值不是一般的数值,而是一些内存单元地址编号(即地址),这时,一般称之为参数的地址传递。在这种参数传递形式中,无论在函数的定义中出现的形参还是在调用语句中出现的实参,都是代表一些内存单元地址编号(即地址数值),而不是一般的数值。C+中的参数地址传递情况一般有如下几种:实参可以是一个有确定值的普通变量的地址,或者是一个已经初始化的指针变量;或者是一个初始化的数组名;或者是
23、一个具体的函数名。而形参可以是一个任意普通变量的地址,或是一个任意指针变量,或是一个任意的数组名,或是一个指向函数的指针变量(对应于实参是具体函数名)。 实际上,这种参数传递机制就是在函数调用时把一个内存单元地址传递给形参,使形参也具有实参的内存单元地址(即两者对应同一个内存单元),称作形参和实参地址结合,两者合二为一。这样一来,任何时候形参的值等于实参的值;而实参的值也等于形参的值。因此,形参在函数中发生变化后,也会引起实参跟着变化(因为它们是捆绑在一起的,一体化的)。这就意味着按地址传递的方式,在调用刚开始时实参的值影响了形参;而在被调函数执行过程中形参值若发生了变化,它也会影响实参的值变
24、化。即机制是双向影响,这与普通值传递方式的单向影响机制形成对比。3.3.4 带默认值的参数C+语言中,允许在函数声明或定义时给一个或多个参数指定默认值。通语言中,允许在函数声明或定义时给一个或多个参数指定默认值。通常调用函数时,要为函数的每个形式参数给定相应的值。例如下面的常调用函数时,要为函数的每个形式参数给定相应的值。例如下面的delay( )函数作用是作时间延迟,在没有使用默认值参数的情况下,是按函数作用是作时间延迟,在没有使用默认值参数的情况下,是按如下普通方式声明和定义的:如下普通方式声明和定义的: 【例例3.5】 延迟函数的使用。延迟函数的使用。#include /*ex3_5.c
25、pp* void delay(int loop); /函数声明函数声明void main( ) coutbeginendl; delay(1000); /函数调用函数调用 coutendendl;void delay(int loop) /函数定义函数定义 if (loop=0) return; for(int i=0;iloop;i+) coutiendl; /输出输出i的值为了清楚看到程序执行的情况的值为了清楚看到程序执行的情况 如果每次调用延迟时间基本一样,可以使用C+中默认值参数函数形式来解决问题。解决的方法就是在函数的声明(或定义)时给定默认值即可。具体做法只要把delay()函数的
26、声明改为下列形式: void delay(int loop=1000); /指定参数默认值为1000以后如果需要延迟相同时间1000,都可以不必指定实参的值而直接调用函数:delay(); /不给定实参,形参将得到默认值1000 delay(500); /给定实参,形参将得到所给的值500 如果有多个形参,可以使每个形参有一个默认值;也可以只对一部分形参指定默认值。如前面的求圆柱体体积的函数volume,可以这样声明:float volume(float r,float h=8.5); /只对形参h指定默认值8.5这时函数调用可采用以下形式:volume(6.0); /相当于volume(6.
27、0,8.5)volume(6.0,7.2); /r的值为6.0,h的值为7.2 C+中实参和形参的结合是从左至右进行的,第1个实参必然与第1个形参结合,第2个实参必然与第2个形参结合,。因此,指定默认值的参数必须放在参数列表中的最右边。3.4 函数的嵌套调用和递归调用 C+不允许对函数作嵌套定义,也就是说在一个函数中不能完整地包含另一个函数。在一个程序中每一个函数的定义都是互相平行和独立的。虽然C+不能嵌套定义函数,但可以嵌套调用函数,也就是说,在调用一个函数的过程中,又调用另一个函数。见图3.2示意。 图3.2 在程序中实现函数嵌套调用时,需要注意的是: 在调用函数之前,需要对每一个被调用的
28、函数作声明(除非定义在前,调用在后)。【例3.6】编程求组合,其中求组合的功能要求用函数完成。分析:根据组合的计算公式,知组合函数有两个形参:m和n,可以用自定义函数comb(int n,int m)表示求组合。而在comb函数中需要3次计算阶乘,如果定义函数fac(k)求k的阶乘,然后在comb函数中调用fac函数,可以使程序代码简单,只要在comb函数中写一个语句“c=fac(m) / (fac(n)*fac(m-n) );”即可求出组合值。【例3.6】 程序代码如下:#include /*ex3_6.cpp*using namespace std;long fac(int k) / 定义
29、求阶乘的函数long f=1; int i; for(i=1;i=k;i+) f = f*i; return f;long comb(int n, int m) /定义组合函数long c; c=fac(m)/(fac(n)*fac(m-n);/ 嵌套调用阶乘函数 return c;void main() int n,m; long c;coutplease input two integer numbers:m,nmn;c=comb(n,m); / 调用组合函数combcoutc=c0,可用n*fac(n-1)表示,即fac(n)的函数体内将递归调用fac()本身;但一旦参数n为0时,则终止
30、调用函数自身并给出函数值1。程序如下: 101 !0nnn( n)n! =using namespace std;#include /*ex3_7.cpp*long fac(int n)long f; if (n=0) f=1; else f=n*fac(n-1); / 递归调用,求(n-1)! return f;void main( )long y; int n; coutplease input a integer n n; y=fac(n); /调用fac(n)求n!coutn=n,y=yendl;运行时,如果输入:3, 运行结果如下:n=3, y=6递归调用及返回过程如图3.5所示,图
31、中的数字序号表示递归调用和返回的先后顺序。 图3.5 求3!的递归过程 fac(3 )fac( )n=33*fac(2)return 6main( )fac( )n=22*fac(1)return 2n=0fac(0)=1return 1n1*fac(0) return 1fac( )fac( )从求n!的递归程序中可以看出,递归定义有两个要素:(1) 递归终止条件。也就是所描述问题的最简单情况,它本身不再使用递归的定义,即程序必须终止。如上例,当n=0时,fac(n)=1,不再使用f(n-1)来定义。(2) 递归定义使问题向终止条件转化的规则。递归定义必须能使问题越来越简单,即参数越来越接近
32、终止条件的参数;达到终止条件参数时函数有确定的值。如上例,f(n)由f(n-1)定义,越来越靠近f(0),即参数越来越接近终止条件参数0;达到终止条件参数时函数有确定的值是f(0)=1。 【例3.8】 汉诺塔问题 汉诺塔(Tower of Hanoi)问题据说来源于布拉玛神庙。该问题的装置如图3.6所示(图上仅画三个金片以简化问题的原理,原问题有64个金片),底座上有三根金钢石的针,第一根针a上放着从大到小64个金片。解决该问题就是要想法把所有金片从第一根针a上移到第三根针c上,第二根针b作为中间过渡。要求是每次只能移动一个金片,并且任何时候不允许大的金片压在小的金片上面。 图3.6 三个金片
33、的汉诺塔问题装置 abc1. 本问题的递归终止条件。如果只有1个盘,显然问题的解就很明显是:直接把金片从a移到c。因此终止条件是n = 1;终止条件对应的操作是直接把金片从a移到c,示意ac。2. 本问题的递归分析:移动n个金片从a到c,必须先将n-1个金片从a借助c移动到b,移动n-1个金片与原问题相同,但规模变小,即向终止条件接近,因此,此问题可以用递归过程完成。递归过程可以用如下步骤表示:(1)将n-1个金片从a经过c移动到b。(2)将第n个金片从a直接移动到c。(3)再将n-1个金片从b经过a移动到c。 一般地,设将n个金片从x针借助y针移动到z针的函数原形为:void hanoi(i
34、nt n,char x,char y,char z)根据解题步骤,可以写出求解n个金片的汉诺塔函数如下:#include /*ex3_8.cpp*using namespace std;void hanoi(int n,char x,char y,char z) if (n=1) /n=1时,直接将金片从x移动到z cout x z 1时 hanoi(n-1,x,z,y); /先将n-1个金片从借助z移动到y cout x z 1时,就递归调用hanoi(),每次n减1。最后当n=1时,直接移动该金片就可以了。 主函数如下: void main() int n; cout input n: n
35、; hanoi(n,a,b,c); / n个金片从a针借助b针移动到c针 虽然递归调用在写程序时很简单,但执行起来却很复杂(时间、存储空间都开销大)。对于汉诺塔问题程序的执行过程分析比较复杂,有兴趣的读者可参阅教材对3个盘情景的分析(图3.7 及其相应文字叙述)。 调用函数时需要一定的时间和空间的开销。下图表示的是一般函数调用的过程。一般函数调用过程3.5 内置函数3.5.1 内置函数的作用内置函数也称内联函数、内嵌函数。引入内置函数的目的是为了提高程序中函数调用的效率。C+提供一种提高效率的方法,即在编译时将所调用函数的代码直接嵌入到主调函数中,而不是将流程转出去。这种嵌入到主调函数中的函数
36、称为内置函数(inline function),又称内嵌函数。在有些书中把它译成内联函数。指定内置函数的方法很简单,只需在函数首行的左端加一个关键字inline即可。【例3.9】 判断用户从键盘输入的系列字符是数字字符还是其它字符的函数is_number()。#include /*ex3_9a.cpp*int is_number(char); /函数声明void main( ) char c; while (c=cin.get()!=n) if (is_number(c) /调用一个小函数coutyou enter a digit n;else cout=0& ch=9)?1:0; 程
37、序中不断到设备中读取数据,频繁调用程序中不断到设备中读取数据,频繁调用is_number()函数。为了避免频函数。为了避免频繁调用函数,提高执行效率,可以将繁调用函数,提高执行效率,可以将【例例3.9】程序改为:程序改为:#include /*ex3_9b.cpp*void main() char c; while (c=cin.get()!=n) if (c=0& c=9)?1:0) /修改处:直接计算表达式修改处:直接计算表达式 coutyou enter a digit n; else coutyou enter a non-digit n; 修改后的程序在if语句中用表达式替换
38、了函数调用。在程序运行上,提高了一些执行效率,因为免去了大量的函数调用开销。但是,由于is_number函数比相应的表达式可读性好,所以修改后的代码可读性降低,尤其若程序中多处出现is_number的替换时,会大大降低可读性。我们希望既要用函数调用来体现其结构化和可读性,又要使效率尽可能地高。为了尽量做到两全其美,C+中引入内置函数这个方法。3.5.2 定义和使用内置函数内置函数的定义格式如下: inline 函数名(形参列表) /函数体 内置函数的声明格式如下:inline 函数名(形参类型表);其实,内置函数只要在开头一次性声明为inline即可,而后面的函数定义仍可写成一般函数定义的形式
39、,编译器也会将函数视为内置函数。对【例3.9】使用内置函数来解决,代码可以写成下列形式:#include /*ex3_9c.cpp*inline int is_number(char);/inline函数声明函数声明void main( ) char c; while (c=cin.get()!=n) if (is_number(c) /调用一个小函数调用一个小函数coutyou enter a digit n;else cout=0& ch=9)?1:0; 关于内置函数的说明:(1)内置函数与一般函数的区别在于函数调用的处理。一般函数进行调用时,要将程序执行到被调用函数中,然后返回到
40、主调函数中;而内置函数在调用时,是将调用部分用内置函数体来替换。(2)内置函数必须先声明后调用。因为程序编译时要对内置函数替换,所以在内置函数调用之前必须声明是内置的(inline),否则将会像一般函数那样产生调用而不是进行替换操作。(3)在内置函数中,不能含有复杂的结构控制语句,如switch、for和while。如果内置函数有这些语句,则编译器将该函数视同一般函数那样产生函数调用。(4)递归函数不能用作内置函数。(5)以后讲到的类中,所有定义在说明内部的函数都是内置函数。3.6.1 变量的作用域 首先将变量按作用域大小分类,可以分为局部变量和全局变量。C+中并不是所有的变量对任何函数都是可
41、知的。一些变量在整个程序或文件中都是可见的,这些变量作用域大,被称为全局变量。一些变量只能在一个函数或块中可见,这些变量作用域小,被称为局部变量。变量作用域的大小和它们的存储区域有关。全局变量存储在全局数据区(也称为静态存储区),它在程序运行的时候被分配存储空间,当程序结束时释放存储空间。局部变量存储在堆栈数据区,当程序执行到该变量声明的函数(或程序块)时才开辟存储空间,当函数(或程序块)执行完毕时释放存储空间。而决定变量作用域大小的根源是它的定义位置。3.6 变量和函数的属性1局部变量局部变量是指定义在函数或程序块内的变量,它们的作用域分别在所定义的函数体或程序块内。下面是一些局部变量的实例
42、:void main()int x ; /x为函数级的局部变量。其作用域为main()函数内 int y ; /y为语句块级的局部变量,其作用域为该块内 void fun( ) char ch ; /ch为函数级局部变量,其作用域为fun()函数内 【例3.10】 局部变量的使用。#include /*ex3_10.cpp* double fun1(double a, double b) /fun1函数中3个局部变量a、b、c double c; c=a+b; return c; double fun2(double a,double b) /fun2函数中3个局部变量a、b、c double
43、 c; c=a*b; return c;void main()/main函数中3个局部变量a、b、cdouble a,b,c; coutab; c=fun1(a,b); couta+b=cendl; c=fun2(a,b); couta*b=cendl;程序运行结果如下:input two numbers:3.5 4 a+b=7.5 a*b=14注意:不同范围的局部变量可以同名,但同一范围内不允许同名变量出现(否则有冲突);上例每个函数中都定义了相同名称的局部变量,比如有3个a;但这个范围中定义的a与另一个范围中定义的a并不是同一个对象,即“此a非彼a”。全局变量是定义在函数以外的变量(一般的
44、全局变量也称外部变量,而一类特殊的全局变量则叫静态全局变量),它们原则上对整个文件的函数都是可见的,甚至对本程序的其它文件的函数也可见;但通常条件下的全局变量作用域是从定义变量的位置到该文件的结束。 2全局变量【例3.11】 全局变量的使用#include /*ex3_11.cpp* int a; /a的作用域为整个文件void fun1(); /声明fun1函数void main() coutaendl; /main函数中使用了全局变量afun1(); /调用fun1函数coutaendl; /main函数中再次使用全局变量avoid fun1() a=5; / fun1函数中使用全局变量a
45、 程序运行结果如下:05 说明:(1)上例中,在主函数中第一次输出变量a时,a还没有赋值,但是执行结果显示0。 (2)全局变量可以定义在任何位置,但其作用域是从定义的位置开始到文件的末尾。而定义在文件中间的全局变量就只能被其下面的函数所使用,全局变量定义之前的所有函数不会知道该变量。 (3)全局变量为函数间数据的传递提供了通道。由于全局变量可以被其定义后的函数所使用,所以可以使用全局变量进行函数间数据的传递。而且这种传递数据的方法可以传递多个数据的值。【例例3.12】 分别写两个函数求给定两个数的最大公约数和最小公倍数。其分别写两个函数求给定两个数的最大公约数和最小公倍数。其中,要求用全局变量
46、存放最大公约数和最小公倍数。中,要求用全局变量存放最大公约数和最小公倍数。分析:由于求最小公倍数要依赖于最大公约数,所以应先求最大公约数。分析:由于求最小公倍数要依赖于最大公约数,所以应先求最大公约数。故应将求最大公约数的函数写在前面,求最小公倍数的函数放在后面。故应将求最大公约数的函数写在前面,求最小公倍数的函数放在后面。#include /*ex3_12.cpp* int greatest_common_divisor(int,int);/声明求最大公约数的函数声明求最大公约数的函数int lease_common_multiple(int,int); /声明求最小公倍数的函数声明求最小公
47、倍数的函数int max , min; /全局变量分别存放最大公约数、最小公倍数全局变量分别存放最大公约数、最小公倍数void main( ) int a; int b; coutab; greatest_common_divisor(a,b); lease_common_multiple(a,b); cout the greatest common divisor is :maxendl; /使用全局变量使用全局变量max cout the lease common multiple is :minendl; /使用全局变量使用全局变量minint greatest_common_divis
48、or(int x,int y) int t;int r; if (xy) t=x;x=y;y=t; r=x%y; while(r!=0) x=y; y=r; r=x%y; max=y; return max;/使用全局变量使用全局变量maxint lease_common_multiple(int x,int y) min=x*y/max; /使用全局变量使用全局变量max求全局变量求全局变量min, return min; /返回全局变量返回全局变量min 程序运行结果如下: input a,b:36 15 the greatest common divisor is :3the lease
49、 common multiple is :180本例中利用全局变量max和min存储最大公约数和最小公倍数。最大公约数是在函数greatest_common_divisor( )中赋给max的;最小公倍数是在函数lease_common_multiple()中赋给min的,而max和min又在main()函数中使用。本程序的整个过程就是利用全局变量max和min由greatest_common_divisor()函数和lease_common_multiple()函数向main()函数传递数据实现的。(4)其它文件也可以使用文件外定义的全局变量,但要求该变量是外部变量类型的全局变量,而且要求在
50、使用该变量的文件中要有对该变量的声明。外部变量跨文件使用的例子,请参考本章后面的【例3.16】。(5)全局变量降低了函数的通用性,建议不在必要时不要使用全局变量。因为如果函数在执行的时候使用了全局变量,那么其他程序使用该函数时也必须将该全局变量一起移过去。另外,全局变量在程序执行的全部过程都占用存储空间,而不是需要时才开辟存储空间。 3重名局部变量和全局变量作用域规则在在C+中,虽然不允许相同作用域的变量同名,中,虽然不允许相同作用域的变量同名,但允许不同作用域的变量同名。但允许不同作用域的变量同名。那么在一个特定的位置引用到某个有重名的变那么在一个特定的位置引用到某个有重名的变量时,到底是使
51、用哪个变量呢?量时,到底是使用哪个变量呢?这由如下这由如下重名变量作用域规则重名变量作用域规则决定:在某个作决定:在某个作用域范围内定义的变量在该范围的子范围内可用域范围内定义的变量在该范围的子范围内可以定义重名变量,这时原定义的变量在子范围以定义重名变量,这时原定义的变量在子范围内是不可见的,但是它还存在,只是在子范围内是不可见的,但是它还存在,只是在子范围内由于出现了重名的变量而被暂时隐藏起来,内由于出现了重名的变量而被暂时隐藏起来,过了子范围后,它又是可见的。过了子范围后,它又是可见的。 【例3.13】 函数中变量和程序块中变量重名#include /*ex3_13.cpp*void m
52、ain( ) int a=1,b=2,c=3; couta,b,cendl; int b=4; couta,b,cendl; a=b; int c; c=b; couta,b,cendl; couta,b,cendl; couta,b,cendl; 程序运行结果如下:1,2,31,4,34,4,44,4,34,2,3 【例3.14】 全局变量和局部变量重名#include /*ex3_14.cpp* void fun1( ); float x=3.5; void main( ) float x; coutx; coutx in main is :xendl; fun1( ); coutx in
53、 main is :xendl; void fun1() coutx in fun1 is :xendl; 运行结果:input a data:6.6 x in main is :6.6x in fun1 is :3.5x in main is :6.6 从变量的空间属性考虑变量有作用域的概念。那么,从变量的时间属性考虑变量还有存储期(storage duration,也称生命期)的概念,存储期即变量值在内存中存在的时间。而变量的存储期是由变量的存储类别决定的,有两种存储类别:动态存储方式与静态存储方式。 3.6.2 变量的生存期 1短生存期变量动态存储方式所谓动态存储方式,是指在程序运行期间
54、动态地分配存储空间给变量的方式。这类变量存储在动态存储空间(堆或栈),执行其所在函数或程序块时开辟存储空间,函数或程序块结束时释放存储空间,生存期为函数或程序块的运行期间,主要有:函数的形参和函数或程序块中定义的局部变量(未用static声明)。使用动态存储方式的变量有两种:自动变量和寄存器类变量。 (1)自动变量:函数中的局部变量默认是自动变量,存储在动态数据存储区。自动变量可以用关键字auto作为存储类别的声明。自动变量的生存期为函数或程序块执行期间,作用域也是其所在函数或程序块。例如:int fun() auto int a; /a为自动类变量 实际编程过程中,关键字“auto”可以省略
55、。例如上述自动变量也可声明为下面形式:int fun() int a; (2)寄存器变量:寄存器变量也是动态变量,可以用register作为存储类别的声明。寄存器变量存储在CPU的通用寄存器中,因此访问效率高。寄存器变量的生存期和作用域为其定义所在的函数或程序块。一般情况下,将局部最常用到的变量声明为寄存器变量,如循环变量。下面main()函数中的循环变量i就使用了寄存器变量:#include void main() register int i; int sum=0; for(i=1;i=100;i+) sum+=i; cout1+2+.+100=sumendl;程序运行结果如下:1+2+.
56、+100=5050寄存器变量的使用应注意以下问题:(1)寄存器变量不宜定义过多。计算机中寄存器数量是有限的,不能允许所有的变量都为寄存器变量。如果寄存器变量过多或通用寄存器被其他数据使用,那么系统将自动把寄存器变量转换成自动变量。(2)寄存器变量的数据长度与通用寄存器的长度相当。一般是char型和int型变量。 2长生存期变量静态存储方式所谓静态存储方式,是指在程序运行期间分配固定的存储空间给变量的方式。这类变量存储在全局数据区,当程序运行时开辟存储空间,程序结束时释放存储空间,生存期为程序运行期间。采用静态存储方式的变量有:全局变量(含外部变量、静态全局变量)和静态局部变量。(1)外部变量:
57、外部变量就是只用数据类型关键字而未用static关键字定义的全局变量。存储在全局数据区,生存期为程序执行期间。如果不对外部变量另加声明,则它的作用域是从定义点到所在文件的末尾。其实,外部变量不管在何处定义,可以通过用extern关键词加以声明后将其作用域扩展到整个程序,声明语句格式为:extern 外部变量名; 这种扩展作用域的声明语句有两种情况用到:对文件内容后面位置所定义的外部变量x,将其作用域扩展到本文件前面的函数。这时只要在该文件的前面用extern对该变量x声明即可,这叫做提前引用声明。对本程序的另一个源文件B中定义的外部变量y,要想扩展到本文件A中使用,这时只要在本文件A的前面用e
58、xtern对该变量y声明即可,不妨称此为跨文件引用声明。 【例3.15】 对定义在同一文件中外部变量,作提前引用声明以扩展其使用范围到文件前面。#include /*ex3_15.cpp* extern x; /提前引用声明void main() x=4; coutxendl;int x; /外部变量x的定义 【例3.16 】对定义在另一文件中的外部变量,作跨文件引用声明以扩展其作用域到本文件。文件file1.cpp的内容:#include /*ex3_16.cpp*extern w; /跨文件引用声明void main() coutwendl; /使用file2.cpp文件中定义的变量w 文
59、件file2.cpp的内容:int w=10; /外部变量w的定义int fun(int x,int y)return (x+y);程序运行结果如下:10 (2)静态变量:静态变量存储在全局数据区,使用static声明。静态变量有两种:静态局部变量和静态全局变量。 静态局部变量是在定义局部变量时开头再添加一个static关键字所定义的。静态局部变量的特点是:程序执行时,为其开辟存储空间直到程序结束,但只能被其定义所在的函数或程序块所使用。所以静态局部变量的生命周期为程序执行期间,作用域为其定义所在的函数或程序块内。如果没有定义静态局部变量的初始值,系统将自动初始化为0。 【例3.17】 静态局
60、部变量的使用#include /*ex3_17.cpp* void fun();void main() int i; for(i=0;i3;i+) fun(); void fun()int a=0; static int b=0; /定义静态局部变量定义静态局部变量 a=a+1; b=b+1; couta,bendl;程序运行结果如下:1,11,21,3 静态全局变量是在定义全局变量时开头再添加一个static关键字所定义的。静态全局变量的特点是:程序执行时,为其在全局数据区开辟存储空间并初始化为0,直存储期仍与外部变量一样一直延续到程序结束。但其作用域受限于只能被其定义所在的文件使用,而不能借助前面所讲的跨文件引
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 乙方有利租赁合同范例
- 买卖无证房合同范例
- 合同签订及履行管理办法
- 超市食品贮存与保质期管理规定
- 学生网络道德素养的培育与提升
- 2025山西地质集团有限公司物测院等3家二级子公司招聘37人笔试参考题库附带答案详解
- 2025国网中兴有限公司高校毕业生招聘(第一批)笔试参考题库附带答案详解
- 2024年中煤电力有限公司所属企业招聘29人笔试参考题库附带答案详解
- 2024年下半年浙江瓯海城市建设投资集团有限公司招聘拟聘用人员(二)笔试参考题库附带答案详解
- 黄焱医院管理学
- 增演易筋洗髓内功图说(校对勘误版)
- 中国铁路总公司《铁路技术管理规程》(高速铁路部分)2014年7月
- 清明节主题班会PPT模板
- ART-850A系列数字式厂用变保护测控装置技术说明书
- 红色大气中考百日誓师大会PPT模板
- 2022年全国计算机一级EXCEL操作题
- 上海美创力喷码机简易操作及维护
- 维语宗教事务条例(2015)
- 悬挑式卸料平台作业的风险评价结果
- 红河学院本科生毕业论文模板
- ZY10000╱28╱62掩护式液压支架设计说明书
评论
0/150
提交评论