版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第3章函数和编译预处理优化第2章的内容,把功能上独立、经常使用的程序片段写成函数,在需要时调用这个函数,避免在多个需要的地方重复书写同样的程序片段。这也是公用、通用程序共享的方法。3.1函数概述
3.2函数的定义和调用
3.3函数的参数传递
3.4函数的嵌套调用和递归调用
3.5内置函数
3.6变量和函数的属性
3.7编译预处理
3.1概述把一个大程序划分为若干个程序模块(小程序单位
),每一个模块实现一部分功能。不同的程序模块可以由不同的人来完成。每个程序模块可以单独进行编译,如果发现错误,可以在本程序模块范围内查错并改正。这就是程序中运用函数的思想。把实现某一特定功能的相关语句按某种格式组织在一起形成一个程序单位,并给程序单位取一个相应的名称,这样的一个程序单位就叫函数(function)。函数有时也被称作例程或过程;而给程序单位所起的相应名称被称作函数名。
任何程序必须有一个、且只有一个主函数main();但可以有n个自定义函数(n≥0)。图3.1是一个程序中函数调用的示意图。图3.1main()func1()func2()func3()func5()func4()例3.1在主函数中调用其他函数。//*****ex3_1.cpp*****#include<iostream>usingnamespacestd;doubleS1,S2;//定义变量S1和S2分别存放两数和与两数积doubleadd(doublex,doubley) //定义add()函数{S1=x+y;returnS1;}doublemultiply(doublex,doubley) //定义multiply()函数{S2=x*y;returnS2;}voidoutput(void) //定义output()函数{cout<<"两数和S1="<<S1<<","<<"两数积S2="<<S2<<endl;} //输出结果voidmain(void){doublea,b;cout<<"请输入两个数:"<<endl;cin>>a>>b;S1=add(a,b); //调用add()函数
S2=multiply(a,b); //调用multiply()函数
output();//调用Output()函数}
若用户从键盘输入的数是9.05.0,则运行结果如下:请输入两个数:9.0
5.0↙两数和S1=14,两数积S2=45
从用户使用的角度看,函数有两种:(1)系统函数,即库函数。这是由编译系统提供的,用户不必自己定义这些函数,可以直接使用它们。(2)用户自己定义的函数。用以解决用户的专门需要。从函数的形式看,函数分两类:(1)无参函数。调用函数时不必给出参数。(2)有参函数。在调用函数时,要给出参数。在主调函数和被调用函数之间有数据传递。3.2函数的定义和调用3.2.1定义函数的一般形式定义函数的一般形式如下:类型标识符函数名([形式参数列表]){声明语句执行语句}(1)对库函数的声明在程序文件头用#include语句将与库函数有关的文件包含到本程序中来,就完成了对库函数的声明。(2)对自定义函数的声明须在调用某自定义函数之前写如下声明语句:函数类型关键字函数名([参数1类型,参数1名称][,参数2类型,参数2名称][…]);
3.2.2函数的声明(2)对自定义函数的声明(续)也可以用下列简略式声明:函数类型关键字函数名([参数1类型][,参数2类型][,…]);函数类型关键字函数名([参数1类型,标识符1][,参数2类型,标识符2][,…]);C++的函数原型。其中,标识符可用任何合法名称。3.2.2函数的声明【例3.2】函数声明示例:设被调函数area()和volum()分别求园面积和圆柱体体积。程序代码如下://*****ex3_2.cpp*****#include<iostream>usingnamespacestd;voidmain(){doublevolum(float,float); //声明求圆柱体体积的volum()函数
doublearea(floatr); //声明求园面积的area()函数
floatr,h;
doubles,v;cout<<"pleaseinputr,h:";cin>>r>>h;s=area(r);v=volum(r,h);cout<<"s="<<s<<","<<"v="<<v<<endl;}
doublevolum(floatx,floaty) //定义volum()函数{doublearea(floatr);//声明求园面积的area()函数
doublez1,z2;z1=area(x);z2=z1*y;return(z2);
}doublearea(floatx) //定义area()函数{doublez;z=3.14*x*x;return(z);}程序的运行结果如下:pleaseinputa,b:
10.02.0↙s=314,v=628
说明:(1)对函数的定义和函数声明是两回事,不要混淆。(2)之所以函数原型中可以省略形式参数的名称,是因为形式参数的名称是无关紧要的,且在调用前形参并不存在。(3)函数声明语句的位置。函数声明语句可以放在主调函数中,也可放在函数外面,只要出现在调用语句之前即声明有效。3.2.3函数的返回值(1)函数的返回值是通过函数中的return语句获得的。return语句将被调用函数中的一个确定值带回主调函数中去。return语句后面的括号可以要,也可以不要。return后面的值可以是一个表达式。(2)函数值的类型。应当在定义函数时的首行开头指定函数值的类型。(3)如果函数值的类型和return语句中表达式的值不一致,则以函数类型为准,即函数类型决定返回值的类型。对数值型数据,可以自动进行类型转换。编译系统对声明语句并不检查参数名;因此参数名是什么都无所谓。上面程序中的声明也可以写成:floatadd(floata,floatb);
//参数名不用x、y,而用a、b,效果完全相同。应当保证函数原型声明语句与函数首部写法上的一致,即函数类型、函数名、参数个数、参数类型和参数顺序必须相同。说明:前面已说明,如果被调用函数的定义出现在主调函数之前,可以不必加以声明。(2)函数声明的位置可以在函数内,也可以在函数之外。例如,若下列inti(float,float)函数被其它所有函数调用,可以只写一个声明语句--写在所有函数外面;若写在函数内部,则每个函数内部都要写声明语句:
charletter(char,char);//函数声明语句floatf(float,float);//函数声明语句inti(float,float);//函数声明语句intmain()//在main函数中不必作声明而可以调用以上三个函数{…}charletter(charc1,charc2)//定义letter函数{…}floatf(floatx,floaty)//定义f函数{…}
inti(floatj,floatk)//定义i函数{…}如果一个函数被多个函数所调用,用这种方法比较好,不必在每个主调函数中重复声明。函数调用的一般形式:函数名([实际参数列表])如果是调用无参函数,则没有“实际参数列表”,但函数名后面的括号()不能省略。如果实际参数列表包含多个实参,则各参数间用逗号隔开。实参与形参的个数应相等,类型应匹配(相同或赋值兼容)。实参与形参按顺序对应,一对一地传递数据。但应说明,如果实参表列包括多个实参,对实参求值的顺序并不是确定的。3.2.4函数的调用按函数在语句中的作用来分,可以有以下3种函数调用方式:1.函数语句把函数调用单独作为一个语句,并不要求函数带回一个值,只是要求函数完成一定的操作。如【例3.1】中的print_word()函数调用语句。2.函数表达式函数出现在一个表达式中,这时要求函数带回一个确定的值以参加表达式的运算。如:c=2*max(a,b);3.函数参数函数调用作为一个函数的实参。如m=max(a,max(b,c));//max(b,c)是函数调用,其值作为外层max函数调用的一个实参函数调用的方式3.3函数的参数传递
形参:在定义函数时函数名后面括号中的变量名称为形式参数(formalparameter),简称形参。形参是无内存单元(因而不存在)的任何合法标识符。实参:在调用一个函数时,调用语句的函数名后面括号中的参数称为实际参数(actualparameter),简称实参。实参是实际存在(因而有特定值)的常量、变量或表达式。
【例3.3】形参和实参及其数据传递。//*****ex3_3.cpp*****#include<iostream>usingnamespacestd;doublepow(floatx,intn)
//定义函数pow,求x的n次幂,x和n是形参{inti;doubles=1;for(i=1;i<=n;i++)s=s*x;return(s);}voidmain(){floata;intm;doublec;cout<<"pleaseinputthevaluesofaandm:";cin>>a>>m;c=pow(a,m);
//调用函数pow(),a和m是实参。函数值赋给变量ccout<<"Theresultis:"<<c<<endl;}程序的运行结果如下:pleaseinputthevaluesofaandm:2.03↙Theresultis:8有关形参与实参的说明:(1)在定义函数时指定的形参,在未出现函数调用时,它们并不占内存中的存储单元,因此称它们是形式参数或虚拟参数,表示它们并不是实际存在的数据,只有在发生函数调用时,函数max中的形参才被分配内存单元,以便接收从实参传来的数据。在调用结束后,形参所占的内存单元也被释放。(2)实参可以是常量、变量或表达式,如max(3,a+b);但要求a和b有确定的值。以便在调用函数时将实参的值赋给形参。(3)在定义函数时,必须在函数首部指定形参的类型,至于形参使用何名字可随意。
(4)实参与形参的类型应相同或赋值兼容。如果提供的实参与形参的类型不一致,则调用时会把实参类型强制转换成形参类型。3.3.2参数的值传递
值传递参数的实现是系统将实参拷贝一个副本给形参,拷贝后两者就断开关系。在被调函数中,形参可以被改变,但这只影响副本中的形参值,而不影响调用函数的实参值。所以这类函数有对原始数据保护的作用。换一句话说,这种参数传递机制是单向影响,即只能由实参将值传给形参(实参影响形参);而形参在函数中的值如果发生修改,不会反过来影响与之对应的实参。【例3.4】参数值传递的演示。//*****ex3_4.cpp*****#include<iostream>usingnamespacestd;intmax(intx,inty) /定义有参函数max,求两数最大值,x和y是形参{floatm;cout<<"x,y的初始值是:"<<x<<","<<y<<endl;m=x>y?x:y;
x=2*x;y=y+1;cout<<"x,y后来的值是:"<<x<<","<<y<<endl;return(m);}voidmain(){inta,b,c;cout<<"请输入两个整数:";cin>>a>>b;c=max(a,b); //调用函数max,a和b是实参,函数值赋给变量ccout<<"两数的较大数是:"<<c<<endl;cout<<"调用函数后的a、b值分别是:"<<a<<","<<b<<endl;}
程序的运行结果如下:请输入两个整数:
38↙x,y的初始值是:
3,8x,y后来的值是:6,9
3.3.3参数的地址传递除了3.3.2小节介绍的值传递参数方式外,函数调用还有一种特殊的值传递形式,即传递的值不是一般的数值,而是一些内存单元地址编号(即地址),这时,一般称之为参数的地址传递。在这种参数传递形式中,无论在函数的定义中出现的形参还是在调用语句中出现的实参,都是代表一些内存单元地址编号(即地址数值),而不是一般的数值。C++中的参数地址传递情况一般有如下几种:实参可以是一个有确定值的普通变量的地址,或者是一个已经初始化的指针变量;或者是一个初始化的数组名;或者是一个具体的函数名。而形参可以是一个任意普通变量的地址,或是一个任意指针变量,或是一个任意的数组名,或是一个指向函数的指针变量(对应于实参是具体函数名)。实际上,这种参数传递机制就是在函数调用时把一个内存单元地址传递给形参,使形参也具有实参的内存单元地址(即两者对应同一个内存单元),称作形参和实参地址结合,两者合二为一。这样一来,任何时候形参的值等于实参的值;而实参的值也等于形参的值。因此,形参在函数中发生变化后,也会引起实参跟着变化(因为它们是捆绑在一起的,一体化的)。这就意味着按地址传递的方式,在调用刚开始时实参的值影响了形参;而在被调函数执行过程中形参值若发生了变化,它也会影响实参的值变化。即机制是双向影响,这与普通值传递方式的单向影响机制形成对比。3.3.4带默认值的参数C++语言中,允许在函数声明或定义时给一个或多个参数指定默认值。例如下面的delay()函数作用是作时间延迟,不使用默认值参数的声明和定义如下(【例3.5】):#include<iostream>//*****ex3_5.cpp*****usingnamespacestd;
voiddelay(intloop);//函数声明voidmain(){cout<<"begin"<<endl;delay(1000);//函数调用cout<<"end"<<endl;}voiddelay(intloop)//函数定义{if(loop==0)return;for(inti=0;i<loop;i++)cout<<i<<endl;//输出i值是为了清楚看到程序执行情况}如果每次调用延迟时间基本一样,可以使用C++中默认值参数函数形式来解决问题。解决的方法就是在函数声明(或定义)时给定默认值即可。具体做法只要把delay()函数的声明改为下列形式:voiddelay(intloop=1000);//指定参数默认值为1000以后如果需要延迟相同时间1000,都可以不必指定实参的值而直接调用函数:delay();//若不给定实参,形参将得到默认值1000delay(500);//若给定实参,形参将得到所给的值(500)如果有多个形参,可以使每个形参有一个默认值;也可以只对一部分形参指定默认值。如前面的求圆柱体体积的函数volume,可以这样声明:floatvolume(floatr,floath=8.5);//只对形参h指定默认值8.5这时函数调用可采用以下形式:volume(6.0);//相当于volume(6.0,8.5)volume(6.0,7.2);//r的值为6.0,h的值为7.2C++中实参和形参的结合是从左至右进行的,第1个实参必然与第1个形参结合,第2个实参必然与第2个形参结合,……。因此,指定默认值的参数必须放在参数列表中的最右边。3.4函数的嵌套调用和递归调用
C++不允许对函数作嵌套定义,也就是说在一个函数中不能完整地包含另一个函数。在一个程序中每一个函数的定义都是互相平行和独立的。虽然C++不能嵌套定义函数,但可以嵌套调用函数,也就是说,在调用一个函数的过程中,又调用另一个函数。见图3.2示意。
图3.2
在程序中实现函数嵌套调用时,需要注意的是:在调用函数之前,需要对每一个被调用的函数作声明(除非定义在前,调用在后)。【例3.6】编程求组合,要求用函数完成。分析:根据组合的计算公式,知组合函数有两个形参:m和n,可以用自定义函数comb(intn,intm)表示求组合。而在comb函数中需要3次计算阶乘,如果定义函数fac(k)求k的阶乘,然后在comb函数中调用fac函数,可以使程序代码简单,只要在comb函数中写一个语句“c=fac(m)/(fac(n)*fac(m-n));
”即可求出组合值。【例3.6】程序代码如下:#include<iostream>//*****ex3_6.cpp*****usingnamespacestd;longfac(intk)//定义求阶乘的函数{longf=1;inti;for(i=1;i<=k;i++)f=f*i;returnf;}longcomb(intn,intm)//定义组合函数{longc;c=fac(m)/(fac(n)*fac(m-n));//嵌套调用阶乘函数
returnc;}voidmain(){intn,m;longc;cout<<"pleaseinputtwointegernumbers:m,n"<<endl;cin>>m>>n;c=comb(n,m);//调用组合函数combcout<<"c="<<c<<endl;}主函数调用函数comb();comb()在执行过程中又调用了函数fac()。fac()的调用被嵌套在函数comb()的调用中。在调用一个函数的过程中又出现直接或间接地调用该函数本身的现象,称为函数的递归(recursive)调用。C++允许函数的递归调用。例如:直接递归调用的代码形式如下:intf1()//函数f1的定义{……//函数其他部分
z=f1();//直接调用自身
……//函数其他部分}以上是在函数f1()中,又直接调用了f1()函数,直接递归调用过程如后面的图3.3所示。3.4.2函数的递归调用间接递归调用可以表现为如下形式:intf2()//函数f2的定义{……//f2的其他部分x=f3();//调用f3()……//f2的其他部分
}intf3()//函数f3的定义{……//f3的其他部分y=f2();//调用f2()……//f3的其他部分
}函数f2()中调用了f3(),而f3()中又调用了f2(),相当于f2()间接地调用了f2()。这种调用称为间接递归调用,调用过程如图3.4所示。
图3.3函数的直接递归调用图3.4函数的间接递归调用从图上可以看到,这两种递归调用都是无终止的自身调用。显然,程序中不应出现这种无终止的递归调用,而只应出现有限次数的、有终止的递归调用,这可以用if语句来控制,只有在某一条件成立时才继续执行递归调用,否则就不再继续。包含递归调用的函数称为递归函数。【例3.7】用递归计算n!。分析:n!本身就是以递归的形式定义的:求n!,应先求(n-1)!;而求(n-1)!,又需要先求(n-2)!,而求(n–2)!;又可以变成求(n-3)!,如此继续,直到最后变成求0!的问题,而根据公式有0!=1(这就是本问题的递归终止条件)。由终止条件得到0!结果后,再反过来依次求出1!,2!……直到最后求出n!。设求n!的函数为fac(n),函数体内求n!,只要n>0,可用n*fac(n-1)表示,即fac(n)的函数体内将递归调用fac()本身;但一旦参数n为0时,则终止调用函数自身并给出函数值1。程序如下:#include<iostream>//*****ex3_7.cpp*****usingnamespacestd;longfac(intn){longf;
if(n==0)f=1;elsef=n*fac(n-1);
//递归调用,求(n-1)!returnf;}voidmain(){longy;intn;cout<<"pleaseinputaintegern"<<endl;cin>>n;y=fac(n);//调用fac(n)求n!cout<<"n="<<n<<","<<"y="<<y<<endl;}运行时,如果输入:3运行结果如下:n=3,y=6图3.5求3!的递归过程fac(3)fac()n=33*fac(2)return6main()fac()n=22*fac(1)return2n=0fac(0)=1return1n1*fac(0)return1fac()fac()⑦②①③④⑧⑥⑤
3!递归调用及返回过程如图3.5所示,图中的数字序号表示递归调用和返回的先后顺序。从求n!的递归程序中可以看出,递归定义有两个要素:(1)递归终止条件。也就是所描述问题的最简单情况,它本身不再使用递归的定义,即程序必须终止。如上例,当n=0时,fac(n)=1,不再使用fac(n-1)来定义。(2)递归定义使问题向终止条件转化的规则。递归定义必须能使问题越来越简单,即参数越来越接近终止条件的参数;达到终止条件参数时函数有确定值。如上例,fac(n)由fac(n-1)定义,越来越靠近fac(0),即参数越来越接近终止条件参数0;达到终止条件参数时函数有确定的值是fac(0)=1。【例3.8】汉诺塔问题汉诺塔(TowerofHanoi)问题据说来源于布拉玛神庙。该问题的装置如图3.6所示(图上仅画三个金片以简化问题的原理,原问题有64个金片),底座上有三根金钢石的针,第一根针a上放着从大到小64个金片。解决该问题就是要想法把所有金片从第一根针a上移到第三根针c上,第二根针b作为中间过渡。要求是每次只能移动一个金片,并且任何时候不允许大的金片压在小的金片上面。图3.6三个金片的汉诺塔问题装置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针的函数原形为:voidhanoi(intn,charx,chary,charz)根据解题步骤,可以写出求解n个金片的汉诺塔函数如下:#include<iostream>//*****ex3_8.cpp*****usingnamespacestd;voidhanoi(intn,charx,chary,charz){
if(n==1)//n=1时,直接将金片从x移动到zcout<<x<<"->"<<z<<endl;else
//n>1时{hanoi(n-1,x,z,y);//先将n-1个金片从借助z移动到ycout<<x<<"->"<<z<<endl;//然后将第n个金片从x移到zhanoi(n-1,y,x,z);//再将n-1个金片从y借助x移动到z}}当n>1时,就递归调用hanoi(),每次n减1。最后当n=1时,直接移动该金片就可以了。主函数如下:
voidmain(){intn;cout<<"inputn:"<<endl;cin>>n;hanoi(n,'a','b','c');//n个金片从a针借助b针移动到c针}虽然递归调用在写程序时很简单,但执行起来却很复杂(时间、存储空间都开销大)。对于汉诺塔问题程序的执行过程分析比较复杂,有兴趣的读者可参阅教材对3个盘情景的分析(图3.7及其相应文字叙述)。调用函数时需要一定的时间和空间的开销。下图表示的是一般函数调用的过程。一般函数调用过程3.5内置函数3.5.1内置函数的作用内置函数也称内联函数、内嵌函数。引入内置函数的目的是为了提高程序中函数调用的效率:即在编译时将所调用函数的代码直接嵌入到主调函数中。定义内置函数的方法:只需在函数定义的首行左端加一个关键字inline即可,或者在函数声明语句的开头加inline。
。3.5.2定义和使用内置函数内置函数的定义格式如下:inline
函数名(形参列表){
……//函数体}内置函数的声明格式如下:inline
函数名(形参类型表);其实,内置函数只要在开头一次性声明为inline即可,而后面的函数定义仍可写成一般函数定义的形式,编译器也会将函数视为内置函数。【例3.9】将被调函数max(int,int,int)指定为内置函数。#include<iostream>usingnamespacestd;inlineintmax(int,
int,int);//函数声明语句,注意左端有inlinevoidmain(void){inti=5,j=6,k=7,m;m=max(i,j,k);
//注意这第6行
cout<<"max="<<m<<endl;}inlineintmax(inta,
intb,
intc)//定义max为内置函数{if(b>a)a=b;//求a,b,c中的最大者
if(c>a)a=c;returna;}这样,程序第6行“m=max(i,j,k);”就被置换成下列3行语句:if(j>i)i=j;if(k>i)i=k;m=i;
3.6变量和函数的属性3.6.1变量的作用域3.6.2变量的生存期3.6.3内部函数和外部函数局部变量:在一个函数内部或复合语句中定义的变量,称为局部变量。其有效范围有限,如图示意:3.6.1变量的作用域:局部变量和全局变量说明:(1)主函数main中定义的变量(m,n)也只在主函数中有效。主函数也不能使用其他函数中定义的变量。(2)不同函数中可以使用同名的变量,它们代表不同的对象,在内存中占不同的单元;互不干扰,不会混淆。(3)可以在一个函数内的复合语句中定义变量,这些变量只在本复合语句中有效。(4)形式参数也是局部变量。例如f1函数中的形参a只在f1函数中有效;其他函数不能调用。(5)在函数声明中出现的参数名,其作用范围只在本行的括号内。例如:intmax(inta,intb);
//函数声明中出现的a、b,它们的作用范围只在本行有效。
intmax(intx,inty)//函数定义,形参是x、y{cout<<x<<y<<endl;//合法,x、y在函数体中有效cout<<a<<b<<endl;//非法,a、b在函数体中无效}编译时认为max函数体中的a和b未经定义。全局变量:在函数之外定义的变量是外部变量,称为全局变量(或全程变量)。全局变量的有效范围原则上是:从定义变量的位置开始到本源文件结束。全局变量作用范围示意如下:变量的作用域:变量的有效范围称为变量的作用域(scope)。变量有4种不同的作用域:文件作用域、函数作用域、块作用域、函数原型作用域。文件作用域是全局的,其他三者是局部的。除了变量之外,任何以标识符代表的实体其实也都有作用域,概念与变量的作用域相似。【例3.10】局部变量的使用。//*****ex3_10.cpp*****#include<iostream>usingnamespacestd;
doublefun1(doublea,doubleb)
//fun1函数中有2个局部变量,分别取名a、b{a++;b++;return(a*b);}doublefun2(doublea,doubleb)
//fun2函数中有2个局部变量也分别取名a、b{a--;b--;return(a*b);}voidmain()
//main函数中有2个局部变量,也分别取名a、b{doublea,b;cout<<"inputtwonumbers:";cin>>a>>b;cout<<"fun1(a,b)="<<fun1(a,b)<<endl;cout<<"fun2(a,b)="<<fun2(a,b)<<endl;cout<<"a="<<a<<",b="<<b<<endl;}程序的运行结果如下:inputtwonumbers:3.55↙fun1(a,b)=27fun2(a,b)=10a=3.5,b=52.全局变量凡是定义在函数外面的变量都叫全局变量。全局变量的默认作用域是从该变量的定义位置延续到到该文件的末尾。【例3.11】全局变量的使用。程序的运行结果如下:a的初值=0a的终值=1//*****ex3_11.cpp*****#include<iostream>usingnamespacestd;
inta;
//此a的作用域为整个文件voidfun1(); //声明fun1()函数voidmain()
{cout<<”a的初值=”<<a<<endl;
//main()函数中使用了全局变量afun1(); //调用fun1()函数
cout<<”a的终值=”<<a<<endl;
//main()函数中再次使用了全局变量a}voidfun1(){a=a+1;}
//fun1()函数中使用了全局变量a【例3.12】分别写两个函数求给定两个数的最大公约数和最小公倍数。其中,要求用全局变量存放最大公约数和最小公倍数,而不用函数值返回。//*****ex3_12.cpp*****#include<iostream>usingnamespacestd;
intgcd(int,int); //声明求最大公约数的函数gcd()intlcm(int,int); //声明求最小公倍数的函数lcm()intmax,min;//全局变量max和min分别存放最大公约数、最小公倍数voidmain(){inta;intb;cout<<"请输入a和b的值:";cin>>a>>b;gcd(a,b);lcm(a,b);cout<<a<<”与”<<b<<"的最大公约数是:"<<max<<endl;
//使用maxcout<<a<<”与”<<b<<"的最小公倍数是:"<<min<<endl;
//使用min}
intgcd(intx,inty)
//定义求最大公约数的函数gcd(){intt;intr;if(x<y){t=x;x=y;y=t;}r=x%y;while(r!=0){x=y;y=r;r=x%y;}max=y;returnmax; //使用全局变量max的值为函数返回值}intlcm(intx,inty)//定义求最小公倍数的函数lcm(){min=x*y/max;//使用全局变量max,求全局变量min的值
returnmin;
//使用全局变量min的值为函数返回值}程序的运行结果如下:请输入a和b的值:3615↙36和15的最大公约数是:336和15的最小公倍数是:1803.不同作用域的同名变量引用规则
【例3.13】不同作用域的同名变量引用规则示例。#include<iostream>usingnamespacestd;inta=5;
//全局变量aintfun();//声明一个无参有返回值的函数main()
{inta;
//函数级局部变量aa=1;//此处引用的a是函数级局部变量acout<<a<<”,”;
//此处引用的a是函数级局部变量a{inta;
//程序块级局部变量aa=2;
//此处引用的a是程序块级局部变量acout<<a<<”,”;//此处引用的a是程序块级局部变量a}a=3;
//此处引用的a是函数级局部变量acout<<a<<”,”;//此处引用的a是函数级局部变量acout<<fun()<<endl;}intfun(){returna;}//此处引用的a是全局变量a
程序运行结果是:1,2,3,5
上一节已介绍了变量的一种属性——作用域,作用域是从空间的角度来分析的,变量按照作用域可分为全局变量和局部变量。变量还有另一种属性——存储期(也称生存期)。存储期是指变量在内存中的存在期间。这是从变量值存在的时间角度来分析的。存储期可以分为静态存储期和动态存储期。这是由变量的静态存储方式和动态存储方式决定的。3.6.2变量的生存期:
动态存储方式与静态存储方式所谓静态存储方式是指在程序运行期间,系统对变量分配固定的存储空间。而动态存储方式则是在程序运行期间,系统对变量动态地分配存储空间。先看一下内存中供用户使用的存储空间的情况。这个存储空间可以分为三部分,即:(1)程序区(2)静态存储区(3)动态存储区
数据分别存放在静态存储区和动态存储区中。全局变量全部存放在静态存储区中,在程序开始执行时给全局变量分配存储单元,程序执行完毕就释放这些空间。在程序执行过程中它们占据固定的存储单元,而不是动态地进行分配和释放。在动态存储区中存放以下数据:①函数形式参数;②函数中的自动变量(未加static的局部变量);③函数调用时的现场保护和返回地址等。在C++中变量除了有数据类型的属性之外,还有存储类别的属性。存储类别指的是数据在内存中存储的方法,即分为静态存储和动态存储两大类。具体包含4种:自动的(auto);静态的(static);寄存器的(register);外部的(extern)。根据变量的存储类别,可以知道变量的作用域和存储期。(1)自动变量
凡未附加static关键字定义的局部变量都是自动变量
。也可以用关键字auto作为存储类别的声明。
定义方式:[auto]数据类型关键字变量名;或:数据类型关键字[auto]变量名;1.短生存期变量——动态存储方式
(2)寄存器变量
存储在CPU内部通用寄存器中的变量叫寄存器变量,它们也是动态变量;定义寄存器变量要附加register关键字。
定义方式:register
数据类型关键字变量名;寄存器变量的使用应注意以下问题:(1)寄存器变量不宜定义过多。(2)寄存器变量的数据长度与通用寄存器的长度相当。一般是char型和int型变量。
1.短生存期变量——动态存储方式
(1)外部变量外部变量是指定义在函数外面且没附加static关键字的全局变量。
可以利用extern关键词对文件末尾或文件外定义的外部变量作附加声明,使其作用域扩展到整个程序:即,在想使用外部变量的地方,先写一句下列形式的声明语句:extern外部变量名;
2.长生存期变量——静态存储方式
(1)外部变量①对外部变量做提前引用声明
【例3.14】对定义在同一文件后面位置的外部变量,作提前引用声明,使其作用域扩展到该变量定义点之前。程序代码如下://*****ex3_14.cpp*****#include<iostream>usingnamespacestd;externx; //提前引用声明voidmain()
{x=4;cout<<x<<endl;}
//在变量定义位置之前面引用变量xintx;
//外部变量x的定义位置在文件后部
2.长生存期变量——静态存储方式
(1)外部变量②对外部变量做跨文件引用声明
【例3.15】对定义在B文件中的外部变量,在A文件中作跨文件引用声明,以扩展其作用域到A文件。//*****ex3_15A.cpp*****(文件ex3_15A.cpp的内容)#include<iostream>usingnamespacestd;externw; //跨文件引用声明(因为w是在另一文件定义的)intfun(intx,inty)//定义fun()函数{return(x+y);}voidmain(){cout<<w+fun(3,2)<<endl;}
//引用了ex3_15B.cpp文件中定义的变量w //*****ex3_15B.cpp*****(文件ex3_15B.cpp的内容)intw=10; //外部变量w的定义
程序的运行结果是15。
2.长生存期变量——静态存储方式
(2)静态变量
①静态局部变量。在局部变量定义语句开头再添加一个static关键字,这样定义的局部变量叫静态局部变量。
静态局部变量的生命周期延长了(等于程序整个运行期),但作用域仍为其定义所在的函数或程序块内。
2.长生存期变量——静态存储方式
(2)静态变量
①静态局部变量。【例3.16】使用静态局部变量的例子。//*****ex3_16.cpp*****#include<iostream>usingnamespacestd;voidfun();voidmain(){inti;for(i=0;i<3;i++)
fun();}voidfun(){inta=0;staticintb=0;
//定义静态局部变量
a++;b++;cout<<a<<","<<b<<endl;}
2.长生存期变量——静态存储方式
程序的运行结果如下:1,11,21,3(2)静态变量
②静态全局变量。在全局变量的定义语句开头再添加一个static关键字,这样定义的全局变量叫静态全局变量。
【例3.17】静态全局变量的演示。//*****ex3_17A.cpp*****(文件ex3_17A.cpp的内容)staticintu=10;//定义静态全局变量uvoidfun(){cout<<"Thisisex3_17.cpp";}//*****ex3_17B.cpp*****(文件ex3_17B.cpp的内容)#include<iostream>usingnamespacestd;externu;
//试图对u作跨文件引用声明,此时行不通voidmain(){cout<<u<<endl;}//出现"变量u未定义"错误
2.长生存期变量——静态存储方式
1.内部函数如果某函数只能被函数所在的同一文件中的语句调用,则这样的函数叫内部函数。因此,内部函数不能被同一程序中其他文件的语句调用。内部函数定义时,也是在函数类型前加static,所以也称为静态函数,定义格式如下:static函数类型函数名([参数列表]){
函数体
}3.6.3内部函数和外部函数
【例3.18】静态函数的例子。//*****ex3_18A.cpp*****(文件ex3_18A.cpp中的内容)#include<iostream>usingnamespacestd;staticvoidfun();voidmain()
{fun();}staticvoidfun() //文件ex3_18A.cpp中定义了静态函数fun(){cout<<"这是文件ex3_18A.cpp输出的!"<<endl;}//文件ex3_18B.cpp中也定义名为fun()的静态函数:#include<iostream>usingnamespacestd;staticvoidfun();staticvoidfun(){cout<<"这是文件ex3_18B.cpp输出的!"<<endl;}3.6.3内部函数和外部函数
程序的运行结果如下:这是文件ex3_18A.cpp输出的!2.外部函数外部函数是可以被整个程序各个文件中语句调用的函数。(1)外部函数的定义在函数类型前加存储类型关键字extern,或缺省存储类型关键字extern,定义格式如下:[extern]函数类型函数名([参数列表]){
函数体}
(2)外部函数的声明文件A在需要调用文件B中所定义的外部函数时,需要在文件A中用关键字extern对被调函数提出声明,声明格式如下:extern函数类型函数名(参数类型列表)3.6.3内部函数和外部函数
【例3.19】文件ex3_19A.cpp利用文件ex3_19B.cpp中的外部函数实现求双阶乘。//*****ex3_19A.cpp*****(文件ex3_19A.cpp中的内容)#include<iostream>usingnamespacestd;voidmain(){externdoublefac2(int);//声明将要调用在其他文件中定义的fac2()函数
intn;
cout<<"请输入一整数给变量n:"<<endl;cin>>n;cout<<n<<"!!="<<fac2(n)<<endl;}//*****ex3_19B.cpp*****(文件ex3_19B.cpp中的内容)#include<iostream>usingnamespacestd;externdoublefac2(intm) //定义fac2()函数{intn;doubles;s=1;n=m;while(n>=1)
{s=s*n;n=n-2;}returns;
}
3.6.3内部函数和外部函数
程序的运行结果如下:请输入一整数给变量n:7↙7!!=105一个变量除了数据类型以外,还有3种属性:(1)存储类别:auto、static、register、extern4种存储类别。(2)作用域:指程序中可以引用该变量的区域。(3)生存期:指变量在内存中的存储期限。要注意存储类别的用法。auto,static和register3种存储类别只能用于变量的定义语句中,如:变量属性小结autocharc;//字符型自动变量,在函数内定义staticinta;//静态整型变量定义(局部或外部)registerintd;
//整型寄存器变量,在函数内定义externintb;//声明一个已定义的外部整型变量说明:extern只能用来声明已定义的外部变量,而不能用于变量的定义。一看到extern,就可以断定这是变量声明语句,而不是变量的定义语句。变量属性总结:(1)从作用域角度分,有局部变量和全局变量。它们采用的存储类别如下:●局部变量自动变量,即动态局部变量(离开函数,值就消失)静态局部变量(离开函数,值仍保留)寄存器变量(离开函数,值就消失)形式参数(可以定义为自动变量或寄存器变量)●全局变量静态外部变量(有static,只限本文件引用)一般外部变量(无static,允许其他文件引用)(2)从变量存储期来区分,有动态存储和静态存储两种类型。●动态存储--寿命短自动变量(本函数内有效)寄存器变量(本函数内有效)形式参数●静态存储--寿命长静态局部变量(函数内有效)静态外部变量(本文件内有效)外部变量(其他文件可引用)(3)从变量值存放的位置来区分,可分为●内存中静态存储区静态局部变量静态外部变量(函数外部静态变量)外部变量(可为其他文件引用)●内存中动态存储区:自动变量和形式参数●CPU中的寄存器:寄存器变量(4)关于作用域和存储期的概念:空间、时间。二者有联系但不是同一回事。下页两个图分别是作用域、存储期的示意图。(5)Static的作用:Static加于局部变量定义前,
使变量由动态存储方式改变为静态存储方式--延长生存期;但不改作用域。Static加于全局变量定义前,
使变量缩小作用域(局限于本文件),但仍为静态存储方式(不改生存期)。请注意,用auto,register,static定义变量时,是在变量的数据类型定义基础上加上这些关键字,而不能单独使用。如“statica;”是不合法的,应写成诸如“staticinta;”的形式。3.7编译预处理C++提供的预处理功能主要有以下3种:(1)宏定义(2)文件包含(3)条件编译分别用宏定义命令、文件包含命令、条件编译命令来实现。为了与一般C++语句相区别,这些命令以符号“#”开头,而且末尾不包含分号。宏定义的作用是实现文本替换。有两种格式:不带参数的宏定义;带参数的宏定义。1.不带参数的宏定义不带参数宏定义的格式如下:#define宏名字符串其中,define是关键字,“宏名”是一个标识符,“字符串”是字符序列。该语句的意思是“宏名”代表“字符串”。执行该预处理代码时,编译系统将对程序语句中出现的“宏名”统统用“字符串”替代。3.7.1宏定义【例3.20】本程序中有多处要用到圆周率计算面积、周长、体积,为了方便一次性修改圆周率,将圆周率定义为宏。//*****ex3_20.cpp*****#include<iostream>usingnamespacestd;
#definePI3.14
//定义宏名PI,将用3.14替代voidmain(){doubler,h;cout<<"请输入柱体底面半径r和高度h:";cin>>r>>h;cout<<"园柱体的周长是:"<<2*PI*r<<endl;cout<<"园柱体的底面积是:"<<PI*r*r<<endl;cout<<"园柱体的体积是
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2024年度桥梁工程施工现场管理合同5篇
- 2024年度网络安全维护服务合同协议
- 04年地基工程保险合同2篇
- 2024年度设备供应商施工配合合同
- 2024年度施工合同范本:公共设施建设与工程款支付
- 2024年度融资租赁合同标的:设备租赁与资金回流
- 2024年度公墓文化研究与发展合作协议
- 2024年度游戏开发运营合同
- 2024年度风力发电机组配件购销合同
- 二零二四年度汽车行业人才租赁合同
- GB/T 37970-2019软件过程及制品可信度评估
- 2023届高考模拟作文“巧与拙”导写及范文
- GB/T 32638-2016移动通信终端电源适配器及充电/数据接口技术要求和测试方法
- GB/T 18915.2-2002镀膜玻璃第2部分:低辐射镀膜玻璃
- GB/T 17919-2008粉尘爆炸危险场所用收尘器防爆导则
- 白酒品鉴会邀请函(2篇)
- 企业创新体系建设课件
- 蔬菜主要病虫害识别及防治技术(培训课件)
- 全文《中国式现代化》PPT
- 《红楼梦》深入研读学习任务群设计
- 消毒供应中心专科试题
评论
0/150
提交评论