版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
函数和编译预处理第一页,共九十四页,2022年,8月28日3.1概述函数:
function,功能。执行或承担某一功能的程序段,解答某问题的一段程序。它有特定的组织格式和使用方式。
函数与程序的关系:一个复杂的C语言程序包含多个函数。(主函数main)它们可存放在多个文件中。模块化程序设计思想:
划分程序功能,每个子功能用子函数(主函数之外的函数)来承担,而让主函数去调用(使用)这些子函数从而实现程序的所有功能。每个函数单独设计调试,而且可以重复使用。第二页,共九十四页,2022年,8月28日main()func1()func2()func3()func5()func4()主调函数、调用被调函数(子函数)、被调用参数返回值主调函数和被调函数的关系:上下级关系请问:main()被谁调用?一个程序中函数调用的示意图:第三页,共九十四页,2022年,8月28日#include<iostream>
voidmain(){intn;cin>>n;if(n>1)cout<<“thesumis:”<<sum(n)<<endl;//调用sum()函数}intsum(intn)例3.1编写函数求前n个自然数之和,n的值从键盘输入。函数首部、函数头函数首部/函数头函数体{intk,s=0;for(k=1;k<=n;k++)s=s+k;
return
s;}第四页,共九十四页,2022年,8月28日3.2函数的定义和调用
3.2.1函数定义的一般形式定义一个函数:用语言描述一个函数解答问题的过程,即编写一 个函数。函数的定义:为解答某问题所编写的一段程序,具有特定的格式。返回值类型标识符函数名([形式参数列表])
函数首部、函数头{声明语句执行语句}函数体第五页,共九十四页,2022年,8月28日int
sum(
intn)//s()函数的首部doublef(intx,floaty)函数类型:函数返回值的数据类型,由返回值类型标识符决定
。前面【例3.1】的main函数是无参函数,其头部也可以写成:voidmain()voidmain(void)无返回值的函数,叫空值类型函数。第六页,共九十四页,2022年,8月28日函数分类:从用户使用的角度看,函数有两种:(1)系统函数,即库函数,由编译系统提供,用户不必自己定义这些函数,可以直接使用它们。(2)用户自己定义的函数,用以解决用户的特殊问题。从函数的形式看,函数分两类:(1)无参函数。定义、调用函数时不必给出参数。(2)有参函数。定义、调用函数时,要给出参数。在主调函数和被调用函数之间有数据传递。第七页,共九十四页,2022年,8月28日3.2.2函数的声明(3.2.4后再学)第八页,共九十四页,2022年,8月28日3.2.3函数的返回值(接续函数定义一节)函数的返回值(函数值):函数运行后得到的数据值。通过被调用函数中的return语句获得的。(1)return语句
在有返回值的被调用函数中:return结束函数的运行并将其后表达式的值带回主调函数中。
return表达式;
…无………。(此时函数最后的return;可省略)一个函数中可有多条return语句!但其中只有一条被执行。(2)函数的类型/函数值的类型
由函数首部的返回值类型标识符决定
。(3)如果return语句中表达式值的类型和函数的类型不一致,则以函数类型为准,即函数类型决定返回值的类型。对数值型数据,可以自动进行类型转换。intf(intx){doublez;
…
return
z;}第九页,共九十四页,2022年,8月28日3.2.4函数的调用使用函数。两层含义:一层含义指在设计、编写源程序时使用一个函数(静态)。
注意:如果实参列表包括多个实参,实参中出现表达式时,多个实参因共用某些变量而联系时,实参值可能与实参求值的顺序有关。如:fun(n,
++n);设法避免!调用形式:函数名([实际参数列表])实参与形参的个数应相等,类型应匹配(相同或赋值兼容)。实参与形参按顺序对应,运行时一对一地传递数据。实参可以是常量、变量或表达式。另一层含义指在程序运行过程中执行函数中的语句(动态)。第十页,共九十四页,2022年,8月28日函数调用的具体方式:3种函数调用方式1.函数语句print_word();把函数调用单独作为一个语句,并不要求函数带回一个值,只是要求函数完成一定的操作。2.函数表达式c=2*max(a,b);函数出现在一个表达式中,这时要求函数带回一个确定的值以参加表达式的运算。3.函数参数函数调用作为一个函数的实参。m=max(a,max(b,c));//max(b,c)是函数调用,其值作为外层 //max函数调用的一个实参第十一页,共九十四页,2022年,8月28日
(1)主调函数和被调函数中语句的执行顺序。(执行流程改变、CPU控制权传递)(2)参数值传递方向:实参值形参(3)函数值的返回:调用处调用函数过程(动态含义)中的问题:第十二页,共九十四页,2022年,8月28日intmax(intx,inty){intz;z=(x>y)?x:y;
returnz;}voidmain(void){inta,b,c;cin>>a>>b;c=max(a,b);cout<<“Themaxis”<<c<<endl;}主调函数函数调用函数类型函数名函数体形参列表说明函数值实际参数将实参a,b的值传给被调函数max的形参x,y,计算后将z的值作为函数值返回。第十三页,共九十四页,2022年,8月28日intmax(intx,inty){intz;z=(x>y)?x:y;returnz;}voidmain(void){inta,b,c;cin>>a>>b;c=max(a,b);cout<<“Themaxis”<<c<<endl;}ab23调用处(表达式中)3xy23zc=3不要将函数名做变量用!不能写成max=z;第十四页,共九十四页,2022年,8月28日(2)自定义函数的声明函数类型关键字函数名([参数1类型,参数1名称][,参数2类型,参数2名称][…]);
什么时候需要声明?声明的含义、作用?声明的格式、位置?(1)库函数的声明对库函数的声明已经写在有关包含文件(头文件)中了,因此只要在程序文件首部用
include
指令将这些包含文件包含到本程序中来,就完成了对库函数的声明。#include<cmath>
3.2.2函数的声明(补述)函数的声明形式也叫做函数原型(functionprototype)。它说明了函数的类型、函数名、函数各形式参数类型。函数类型关键字函数名([参数1类型][,参数2类型
][…]);《修改p68》名称任意,可省略第十五页,共九十四页,2022年,8月28日【例3.2】输入圆柱体的底面半径和高,求底面圆面积和体积。设函数area()和volum()分别求圆面积和圆柱体体积。#include<iostream>usingnamespacestd;voidmain(){doublevolum(float,float);//声明
doublearea(floatr);//声明
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)//定义{doublearea(floatr);//声明
doublez1,z2;z1=area(x);z2=z1*y;return(z2);
}doublearea(floatx)//定义{doublez;z=3.14*x*x;return(z);}第十六页,共九十四页,2022年,8月28日3.2.2函数的声明(补述)说明:(1)函数定义和函数声明是两回事,不要混淆。(2)之所以函数原型中可以省略形式参数的名称,是因为形式参数的名称是无关紧要的,且在调用前形参并不存在。(3)函数声明的位置。函数声明可以放在主调函数中,也可放在函数外面,只要出现在函数调用之前即可。第十七页,共九十四页,2022年,8月28日(1)在定义函数时指定的形参,在未出现函数调用时,它们并不占内存中的存储单元,因此称它们是形式参数或虚拟参数,表示它们并不是实际存在的数据;只有在程序运行并发生函数调用时,函数中的形参才被分配内存单元,以便接收从实参传来的数据。在调用结束后,形参所占的内存单元也被释放。3.3函数的参数传递(再次说明)intadd(intx,inty){intz;z=x+y;returnz;}voidmain(void){doublea,b,c;cin>>a>>b;c=add(a,3.5);cout<<“Thesumis”<<c<<endl;}(2)实参与形参的类型应相同或赋值兼容。第十八页,共九十四页,2022年,8月28日
值传递:调用函数时,计算机将函数调用处的实参值传给被调函数的形参,在被调函数执行过程中,形参可以被改变,但不影响函数调用处的实参值。换一句话说,这种参数传递机制是单向影响。3.3.1参数的值传递(前面已介绍)第十九页,共九十四页,2022年,8月28日除了小节介绍的值传递参数方式外,函数调用还有一种特殊的值传递形式,即传递的值不是一般的数值,而是一些内存单元地址编号(即地址),这时,一般称之为参数的地址传递。在这种参数传递形式中,无论在函数的定义中出现的形参还是在调用语句中出现的实参,都是代表一些内存单元地址编号(即地址数值),而不是一般的数值。C++中的参数地址传递情况一般有如下几种:实参可以是一个有确定值的普通变量的地址,或者是一个已经初始化的指针变量;或者是一个初始化的数组名;或者是一个具体的函数名。而形参可以是一个任意普通变量的地址,或是一个任意指针变量,或是一个任意的数组名,或是一个指向函数的指针变量(对应于实参是具体函数名)。3.3.2参数的地址传递(4章)第二十页,共九十四页,2022年,8月28日实际上,这种参数传递机制就是在函数调用时把一个内存单元地址传递给形参,使形参也具有实参的内存单元地址(即两者对应同一个内存单元),称作形参和实参地址结合,两者合二为一。这样一来,任何时候形参的值等于实参的值;而实参的值也等于形参的值。因此,形参在函数中发生变化后,也会引起实参跟着变化(因为它们是捆绑在一起的,一体化的)。这就意味着按地址传递的方式,在调用刚开始时实参的值影响了形参;而在被调函数执行过程中形参值若发生了变化,它也会影响实参的值变化。即机制是双向影响,这与普通值传递方式的单向影响机制形成对比。第二十一页,共九十四页,2022年,8月28日【例3.5】延迟函数的使用。#include<iostream.h>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;}3.3.3带默认值的参数(C++)C++语言中,允许在函数定义或声明时给一个或多个形参指定默认值。这样,后面的函数调用中,可以不给具有默认值的形参设定相应的实参。voiddelay(intloop=1000);voiddelay(intx=1000);delay();//调用
第二十二页,共九十四页,2022年,8月28日
对于教材【例3.2】的求圆柱体体积的函数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.2
如果函数有多个形参,可以对每个或部分形参指定默认值。指定默认值的形参必须放在参数列表中的
最右边。第二十三页,共九十四页,2022年,8月28日课堂总结第二十四页,共九十四页,2022年,8月28日……a函数……b函数………………⑴main函数…………结束(2)(3)(4)(5)(6)(7)(8)(9)
C语言不允许对函数作嵌套定义!3.4.1嵌套调用:在一个函数定义中,可以调用另一个函数。
程序执行时,在调用一个函数的过程中,又调用另一个函数。例如:3.4函数的嵌套调用和递归调用调用a函数调用b函数main()函数直接地调用了a函数,main()函数嵌套地调用了b函数(间接调用)。第二十五页,共九十四页,2022年,8月28日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;}main()分析:定义函数longcomb(intn,intm)求组合数。定义函数longfac(intk)求k的阶乘。comb()fac()#include<iostream>usingnamespacestd;longfac(intk)//定义求阶乘的函数{longf=1;inti;for(i=1;i<=k;i++)f=f*i;returnf;}【例3.6】编程求组合数,第二十六页,共九十四页,2022年,8月28日3.4.2函数的递归调用在调用一个函数的过程中,被调用函数又直接或间接地调用自身,这种调用过程称为函数的递归(recursive)调用。直接递归调用函数的代码形式:intf()//函数f1的定义{……//函数其它部分
z=f();//直接调用自身
……//函数其它部分}在函数f()中,又直接调用了f()函数。第二十七页,共九十四页,2022年,8月28日间接递归调用函数可以表现为如下:intf1()//函数f1的定义{……//f1的其他部分x=f2();//调用f2()……//f1的其他部分}intf2()//函数f2的定义{……//f2的其他部分y=f1
();//调用f1()……//f2的其他部分
}第二十八页,共九十四页,2022年,8月28日函数的直接递归调用函数的间接递归调用
图中调用过程特点:这两种递归调用都是无终止的自身调用?!程序中不应出现这种无终止的递归调用,而只应出现有限次数的、有终止的递归调用!解决:用if语句来控制,只有在某一条件成立时才继续执行递归调用,否则就不再继续。包含递归调用的函数称为递归函数。第二十九页,共九十四页,2022年,8月28日【例3.7】用递归计算n!。求n!,应先求(n-1)!;而求(n-1)!,又需要先求(n-2)!,而求(n–2)!;又可以变成求(n-3)!,如此继续,直到最后变成求1!的问题,而根据公式有1!=1(这就是本问题的递归终止条件)。由终止条件得到1!结果后,再反过来依次求出2!,3!……直到最后求出n!。第三十页,共九十四页,2022年,8月28日【例3.7】用递归计算n!。n!本身就是以递归的形式定义的:#include<iostream>usingnamespacestd;longfac(intn){longf;if(n==1)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;}调用fac(3)求3!时,fac()递归调用及调用返回过程:fac(3)fac()n=33*fac(2)return6main()fac()n=22*fac(1)return2n=1fac(1)return1fac()⑤②①③⑥④第三十一页,共九十四页,2022年,8月28日问题的递归定义(递归求解方法)含有两个要素:(1)递归终止条件。也就是所描述问题的最简单情况,它本身不再使用递归的定义,即程序必须终止。如上例,当n=1时,fac(n)=1,不再使用f(n-1)来定义。(2)使问题向终止条件转化的规则。
递归定义必须能使问题越来越简单,即参数越来越接近终止条件的参数;达到终止条件参数时函数有确定的值。如上例,f(n)由f(n-1)定义,越来越靠近f(1),即参数n越来越接近终止条件参数1;达到终止条件参数时函数有确定的值是f(1)=1。
第三十二页,共九十四页,2022年,8月28日方法一:用循环做方法二:用递归函数做(n=0)(n≥1)Sn=n+Sn-10(递归定义)类似的问题:已知n,求sn=1+2+3+......+n 第三十三页,共九十四页,2022年,8月28日Fibonacci数列:0,1,1,2,3,5,8,13,21,…其数学表达式(递归定义)为:
fibonacci(0)=0n==0fibonacci(1)=1n==1fibonacci(n)=fibonacci(n-1)+fibonacci(n-2)(n>1)longfibonacci(intn){ if(n==0||n==1) returnn; else returnfibonacci(n-1)+fibonacci(n-2);}程序如下所示:
《实验教程》P19三实验思考1:求Fibonacci数列大于t的最小项的值。第三十四页,共九十四页,2022年,8月28日voidmain(){ intn=0,t; longresult,t; cin>>t; result=fibonacci(n); whlie(result<=t){n++; result=fibonacci(n);}cout<<result; }方法:1.递归函数《实验教程:P19三实验思考1》2.用循环做《实验教程:
p90.四,1》3.数组《教材:p140,3》第三十五页,共九十四页,2022年,8月28日求两个数最大公约数的方法1.辗转相除法(p57)2.短除法3.试探法4.递归方法(用函数递归调用做)用g(m,n)表示m、n的最大公约数,请写出则其递归定义。第三十六页,共九十四页,2022年,8月28日递归边界(终止)条件使问题向递归边界(终止)条件转化的规则类似例题习题:强调:上述问题的递归定义具备两个成分:第三十七页,共九十四页,2022年,8月28日递推方法程序描述繁杂,可读性差;主要采用循环结构;逐步执行;当前值的求得总建立在前面求解的基础上;递归方法描述与原始问题(递归公式)比较接近;书写简洁、易读易写;易于分析算法的复杂性和证明算法的正确性;在问题转化时,需要花时间和存储空间将有关的“现场信息”保存起来;当达到终止条件时,系统又需要花时间将有关的“现场信息”恢复以便处理未曾处理完的函数调用。占用存储空间少,执行速度快。递归与递推算法递归函数的适应场合:待求解的问题含有递归关系。第三十八页,共九十四页,2022年,8月28日【例3.8】汉诺塔问题abc汉诺塔(TowerofHanoi)问题据说来源于布拉玛神庙。该问题的装置如图3.6所示(图上仅画三个金片以简化问题的原理,原问题有64个金片),底座上有三根金钢石的针,第一根针a上放着从大到小64个金片。解决该问题就是要想法把所有金片从第一根针a上移到第三根针c上,第二根针b作为中间过渡。要求是每次只能移动一个金片,并且任何时候不允许大的金片压在小的金片上面。图3.6三个金片的汉诺塔问题装置第三十九页,共九十四页,2022年,8月28日1.本问题的递归终止条件。如果只有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。
第四十页,共九十四页,2022年,8月28日一般地,设将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}}第四十一页,共九十四页,2022年,8月28日当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及其相应文字叙述)。第四十二页,共九十四页,2022年,8月28日3.5内置函数(C++,选讲)一般函数调用的过程:调用前:参数传递,保存下条指令地址信息,和CPU状态信息,执行流程转入被调函数。调用完毕:传回函数值,执行流程回到主调函数。函数调用花费额外的时间和空间!如果函数频繁地被调用,如调用出现在循环中,则开销大,影响效率(缺点)。函数的优点之一:便于实现模块化/结构化程序设计的方法。发挥其优点,克服其缺点,采用内置函数第四十三页,共九十四页,2022年,8月28日3.5内置函数(C++,选讲)一般函数调用的过程:C++编译时将被调函数的代码直接嵌入到主调函数中,程序执行时不必将流程转出去。这种嵌入到主调函数中的函数称为内置函数(inlinefunction),又称内嵌函数或内联函数。第四十四页,共九十四页,2022年,8月28日3.5.1内置函数的作用提高程序中函数调用的效率;并保持程序的可读性。
适应场合:如果函数频繁地被调用.指定内置函数的方法:在函数定义首行(或函数说明)的左端加一个关键字inline即可。第四十五页,共九十四页,2022年,8月28日#include<iostream.h>intis_number(char);//函数声明voidmain(){charc;while((c=cin.get())!='\n'){if(is_number(c))//调用一个小函数cout<<"youenteradigit\n";elsecout<<"youenteranon-digit\n";}}intis_number(charch)//函数定义{return(ch>='0'&&ch<='9')?1:0;}【例3.9】判断用户从键盘输入的系列字符是数字字符还是其它字符的函数is_number()。第四十六页,共九十四页,2022年,8月28日程序中不断到设备中读取数据,频繁调用is_number()函数。为了避免频繁调用函数,提高执行效率,可以将【例3.9】程序改为:#include<iostream.h>//*****ex3_9b.cpp*****voidmain(){charc;while((c=cin.get())!='\n'){if((c>='0'&&c<='9')?1:0)//修改处:直接计算表达式
cout<<"youenteradigit\n";elsecout<<"youenteranon-digit\n";}}第四十七页,共九十四页,2022年,8月28日修改后的程序在if语句中用表达式替换了函数调用。在程序运行上,提高了一些执行效率,因为免去了大量的函数调用开销。但是,由于is_number函数比相应的表达式可读性好,所以修改后的代码可读性降低,尤其若程序中多处出现is_number的替换时,会大大降低可读性。我们希望既要用函数调用来体现其结构化和可读性,又要使效率尽可能地高。为了尽量做到两全其美,C++中引入内置函数这个方法。第四十八页,共九十四页,2022年,8月28日内置函数的定义格式如下:inline
类型名函数名(形参列表){
……//函数体}内置函数的声明格式如下:inline类型名函数名(形参类型表);其实,内置函数只要在开头一次性声明为inline即可,而后面的函数定义仍可写成一般函数定义的形式,编译器也会将函数视为内置函数。3.5.2定义和使用内置函数第四十九页,共九十四页,2022年,8月28日对【例3.9】使用内置函数来解决,代码可以写成下列形式:#include<iostream.h>//*****ex3_9c.cpp*****inlineintis_number(char);//inline函数声明voidmain(){charc;while((c=cin.get())!='\n'){if(is_number(c))//调用一个小函数cout<<"youenteradigit\n";elsecout<<"youenteranon-digit\n";}}intis_number(charch)//此处可省掉inline,却被本程序视为inline{return(ch>='0'&&ch<='9')?1:0;}第五十页,共九十四页,2022年,8月28日(1)内置函数与一般函数的区别在于函数调用的处理。一般函数进行调用时,要将程序执行到被调用函数中,然后返回到主调函数中;而内置函数在调用时,是将调用部分用内置函数体来替换。(2)若函数定义部分在调用之后,则内置函数必须先声明后调用。因为程序编译时要对内置函数替换,所以在内置函数调用之前必须声明是内置的(inline),否则将会像一般函数那样产生调用而不是进行替换操作。(3)在内置函数中,不能含有复杂的结构控制语句,如switch、for和while。如果内置函数有这些语句,则编译器将该函数视同一般函数那样产生函数调用。(4)递归函数不能用作内置函数。(5)以后讲到的类中,所有定义在说明内部的函数都是内置函数。关于内置函数的说明:第五十一页,共九十四页,2022年,8月28日3.6变量和函数的属性变量的属性:类型、名称、作用域、生存期
3.6.1变量的作用域
作用域:指变量在空间上的有效范围。1.局部变量局部变量:在函数或程序块(复合语句)内定义的变量。其作用域:相应的函数体或程序块,从定义点到相应的函数体或程序块的尾部。voidf(intt)//t为函数级的局部变量。{intx1;//x1为函数级的局部变量。
……{inty1;//y1为语句块级的局部变量,
……inty2;//y2为语句块级的局部变量,
……
}……intx2;//x2为函数级的局部变量。……}第五十二页,共九十四页,2022年,8月28日【例3.10】局部变量的使用。#include<iostream.h>doublefun1(doublea,doubleb){doublec;//fun1函数中3个局部变量a、b、cc=a+b;returnc;}voidmain()//main函数中3个局部变量a、b、c{doublea,b,c;cout<<"inputtwonumbers:";cin>>a>>b;c=fun1(a,b);cout<<"a+b="<<c<<endl;c=fun2(a,b);cout<<"a*b="<<c<<endl;}局部变量同名现象:不同范围的局部变量可以同名,不同范围的同名局部变量表示不同的数据。但同一范围内不允许两变量同名出现。doublefun2(doublea,doubleb){doublec;//fun2函数中3个局部变量a、b、cc=a*b;returnc;}第五十三页,共九十四页,2022年,8月28日注意:在函数声明中出现的参数名,其作用范围只在本行的括号内。例如:intmax(inta,intb);
//函数声明中出现的a、b,它们的作用范围只在本行有效。
intmax(int,int);
第五十四页,共九十四页,2022年,8月28日定义在函数以外的变量(外部变量,静态全局变量)。作用域:从变量定义处到该文件结尾处。通过声明后,对本程序的其它文件中的函数也可见。2.全局变量第五十五页,共九十四页,2022年,8月28日#include<iostream.h>inta;//a的作用域为整个文件voidfun1();//声明fun1函数voidmain(){cout<<a<<endl;//main函数中使用了全局变量afun1();//调用fun1函数cout<<a<<endl;//main函数中再次使用全局变量a}voidfun1(){a=5;}//fun1函数中使用全局变量a程序运行结果如下:05没有设定初始值的全局变量,编译默认其初值为0。【例3.11】全局变量的使用第五十六页,共九十四页,2022年,8月28日说明:(1)没有人为设定初始值的全局变量,编译将其初值设为0(全局变量默认初始值为0)。(2)全局变量可以定义在任何位置,但其作用域是从定义的位置开始到文件的末尾。
而定义在文件中间的全局变量就只能被其下面的函数所使用,全局变量定义之前的所有函数不会知道该变量。
(3)全局变量为函数之间数据的传递提供了通道。由于全局变量可以被其定义后的函数所使用,所以可以使用全局变量进行函数间数据的传递。而且这种传递数据的方法可以传递多个数据的值。第五十七页,共九十四页,2022年,8月28日gcd(a,b)
maxlcm(a,b)main()
min分析:由于求最小公倍数要依赖于最大公约数,所以应先求最大公约数。故应将求最大公约数的函数写在前面,求最小公倍数的函数放在后面。intgcd(int,int);//声明求最大公约数的函数intlcm(int,int);//声明求最小公倍数的函数intmax,min;//全局变量分别存放最大公约数、最小公倍数voidmain()【例3.12】分别写两个函数求给定两个数的最大公约数和最小公倍数。其中,要求用全局变量存放最大公约数和最小公倍数。第五十八页,共九十四页,2022年,8月28日分析:由于求最小公倍数要依赖于最大公约数,所以应先求最大公约数。故应将求最大公约数的函数写在前面,求最小公倍数的函数放在后面。#include<iostream>intgcd(int,int);//声明求最大公约数的函数intlcm(int,int);//声明求最小公倍数的函数intmax,min;//全局变量分别存放最大公约数、最小公倍数voidmain(){inta;intb;cout<<"inputa,b:";cin>>a>>b;gcd(a,b);lcm(a,b);cout<<"thegreatestcommondivisoris:"<<max<<endl;//使用全局变量max
cout<<"theleasecommonmultipleis:"<<min<<endl;//使用全局变量min}【例3.12】分别写两个函数求给定两个数的最大公约数和最小公倍数。其中,要求用全局变量存放最大公约数和最小公倍数。第五十九页,共九十四页,2022年,8月28日intgcd(intx,inty){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){
min=x*y/max;//使用全局变量max求全局变量min,returnmin;//返回全局变量min}void
gcd(intx,inty){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;}第六十页,共九十四页,2022年,8月28日(4)全局变量降低了函数的通用性,建议不在必要时不要使用全局变量(缺点)。因为如果函数在执行的时候使用了全局变量,那么其他程序使用该函数时也必须将该全局变量一起移过去。另外,全局变量在程序执行的全部过程都占用存储空间,而不是需要时才开辟存储空间。第六十一页,共九十四页,2022年,8月28日3.不同作用域的同名变量引用规则
程序运行结果是:1,2,3,5
【例3.13】不同作用域的同名变量引用规则示例。…inta=5;
//全局变量aintfun();//声明一个函数main()
{inta;
//函数级局部变量a
a=1;//引用是函数级局部变量acout<<a<<”,”;
//此处引用的是函数级局部变量a{inta;
//程序块级局部变量a
a=2;
//此处引用是程序块级局部变量acout<<a<<”,”;//此处引用的是程序块级局部变量a}
a=3;
//此处引用的a是函数级局部变量acout<<a<<”,”;//此处引用的a是函数级局部变量acout<<fun()<<endl;}intfun(){returna;}//全局变量a
重名变量作用域规则:在某个作用域范围内定义的变量,在该范围的子范围内可以定义重名变量,这时原定义的变量在子范围内是不可见的,但是它还存在,只是在子范围内由于出现了重名的变量而被暂时隐藏起来,过了子范围后,它又是可见的。第六十二页,共九十四页,2022年,8月28日3.6.2变量的生存期(较难)变量存储期(生命期):变量(值)在内存中存在的时间。动态存储区(堆栈)静态(全局)存储区寄存器(CPU中)数据存储区(变量存储区)1.短生存期变量――动态存储方式2.长生存期变量――静态存储方式变量存储期长短由变量的存储方式决定。动态存储方式:在程序运行期间动态地分配存储空间给变量的方式。包括:函数的形参,函数或程序块中定义的局部变量(未用static声明)。具体有两种:自动变量、寄存器类变量。静态存储方式:在程序运行期间分配固定的存储空间给变量的方式。存储位置:静态(全局)存储区,生存期:为程序运行期间。包括:全局变量(含外部变量、静态全局变量)、静态局部变量。存储位置:在动态存储空间(堆或栈)或寄存器中。存储期:为函数或程序块的运行期间。
第六十三页,共九十四页,2022年,8月28日存储位置:在动态数据存储区;生存期:函数或程序块执行期间;作用域:其所在函数或程序块。用关键字auto作为存储类别的声明。例如:intfun(){autointa;//a为自动类变量}
关键字“auto”可以省略。上述自动变量也可声明为inta;(1)自动变量:函数中的局部变量默认是自动变量。(2)寄存器变量:局部变量。存储位置:在CPU的通用寄存器中;生存期:作用域:特点:访问效率高,数量较少;用关键字register作为存储类别的声明。例如:voidmain(){registerinti;
…}第六十四页,共九十四页,2022年,8月28日1)寄存器变量不宜定义过多。计算机中寄存器数量是有限的,不能允许所有的变量都为寄存器变量。如果寄存器变量过多或通用寄存器被其他数据使用,那么系统将自动把寄存器变量转换成自动变量。2)寄存器变量的数据长度与通用寄存器的长度相当。一般是char型和int型变量。寄存器变量的使用应注意以下问题:第六十五页,共九十四页,2022年,8月28日2.长生存期变量――静态存储方式全局变量(含外部变量、静态全局变量)和静态局部变量。(1)外部变量:未用static关键字定义的全局变量。如果不对外部变量另加声明,则它的作用域是从定义点到所在文件的末尾。第六十六页,共九十四页,2022年,8月28日用extern关键词加以声明后将外部变量作用域扩展到声明位置,声明语句格式为extern
类型符
外部变量名;//int类型符可省改具体方式:①提前引用声明。扩展外部变量作用域的方式【例3.14】对定义在同一文件中外部变量,作提前引用声明可以扩展其使用范围到文件前面的声明位置。#include<iostream.h>voidmain(){x=4;cout<<x<<endl;}intx;//外部变量x的定义……externintx;//提前引用声明第六十七页,共九十四页,2022年,8月28日②跨文件引用声明。
扩展外部变量作用域的方式【例3.15】对定义在另一文件中的外部变量,作跨文件引用声明以扩展其作用域到本文件。文件file1.cpp的内容:#include<iostream.h>voidmain(){cout<<w<<endl;//使用file2.cpp文件中定义的变量w}文件file2.cpp的内容:intw=10;//外部变量w的定义程序运行结果如下:10externintw;//跨文件引用声明用途:用于多个文件中的函数共享数据!两个文件中不能出现同名的外部变量!
第六十八页,共九十四页,2022年,8月28日(2)静态变量:静态全局变量、静态局部变量
①静态全局变量:在定义全局变量时开头再添加一个static关键字所定义的全局变量。作用域:定义点至定义所在文件的末尾;可以通过提前引用声明扩展其作用域;不能通过跨文件引用声明扩展其作用域(只对本文件有效)。一个文件中定义的静态全局变量与别的文件中定义的同名静态全局变量或同名外部变量没有牵连,互不影响!默认初值:0。第六十九页,共九十四页,2022年,8月28日【例3.17】静态全局变量的演示。文件file1.cpp的内容:#include<iostream.h>staticintu=10;//定义静态全局变量voidfun(){cout<<”Thisisfile1”}文件file2.cpp的内容:#include<iostream.h>externintu;//试图对u作跨文件引用声明,此时行不通voidmain(){cout<<u<<endl;//出现“变量u未定义”错误}第七十页,共九十四页,2022年,8月28日(2)静态变量:静态全局变量、静态局部变量
②静态局部变量:在定义局部变量时开头再添加一个static关键字所定义的局部变量。生命周期:作用域:默认初值:0。第七十一页,共九十四页,2022年,8月28日【例3.16】使用静态局部变量的例子。#include<iostream.h>voidfun();voidmain(){inti;for(i=0;i<3;i++)fun();}voidfun(){inta=0;//定义局部变量
staticintb=0;//定义静态局部变量
a=a+1;b=b+1;cout<<a<<","<<b<<endl;}程序运行结果如下:1,11,21,3第七十二页,共九十四页,2022年,8月28日变量属性总结根据需要设置变量的属性!第七十三页,共九十四页,2022年,8月28日3.6.3内部函数和外部函数(选讲)存储属性(作用域)分别类似静态全局变量、外部变量1内部函数(存储属性类似静态全局变量)如果一个函数只能被本文件中其他函数所调用,它称为内部函数。在定义内部函数时,在函数名和函数类型的前面加static。函数首部的一般格式为static类型标识符函数名(形参表)如staticintfun(inta,intb)内部函数又称静态(static)函数。使用内部函数,可以使函数只局限于所在文件。不同文件中的同名内部函数互不干扰。第七十四页,共九十四页,2022年,8月28日【例3.18】静态函数的例子。文件file1.cpp中的内容:#include<iostream.h>staticvoidfun();voidmain(){fun();}staticvoidfun()//文件file1中定义静态函数,名称为fun{cout<<"thisinfile1"<<endl;}文件file2.cpp中的内容:#include<iostream.h>staticvoidfun()//文件file2中定义静态函数,名称也为fun{cout<<"thisinfile2"<<endl;}程序运行结果如下:thisinfile1第七十五页,共九十四页,2022年,8月28日2外部函数(存储属性类似外部变量)外部函数是可以被整个程序各文件中函数调用的函数。(1)外部函数的定义。在函数类型前加存储类型关键字extern,或缺省存储类型关键字,定义格式如下:[extern]函数类型函数名([参数列表]){
函数体}extern可缺省,即系统默认为extern型。(2)外部函数的(跨文件引用)声明。文件A在需要调用文件B中所定义的外部函数时,需要在文件A中用关键字extern对被调函数提出声明,声明格式如下:extern
函数类型函数名(参数类型列表)第七十六页,共九十四页,2022年,8月28日【例3.19】文件file1.cpp利用别的文件(file2.cpp)中的外部函数实现求阶乘。文件file1.cpp中的内容:#include<iostream>usingnamespacestd;voidmain(){externdoublefac(int);//声明其他文件中定义的外部函数intn;cout<<"pleaseinputaintegerton:"<<endl;cin>>n;cout<<n<<"!="<<fac(n)<<endl;}文件file2.cpp中的内容:#include<iostream>usingnamespacestd;externdoublefac(intm)//定义fac函数extern可以省略{intn;doubles;s=1;for(n=1;n<=m;n++)s=s*n;returns;}程序运行结果如下:pleaseinputaintegerton:5↙5!=120第七十七页,共九十四页,2022年,8月28日什么叫编译预处理?指编译前对源程序中的
特殊命令(预处理命令)进行的一些预处理加工的过程。预处理由编译系统中的预处理程序按源程序中的预处理命令进行。功能:编译预处理命令不是C语句,但它扩展了C++程序的设计环境,提高编程效率。
源程序目标程序可执行程序中间结果编译预处理编译连接编译
C/C++程序提供的编译预处理命令有三种:
1.宏定义
2.文件包含3.条件编译3.7编译预处理第七十八页,共九十四页,2022年,8月28日1不带参数的宏定义用一个指定的标识符来代表一个字符串,它的一般定义形式为
#define
标识符字符串
这种方法使用户能以一个简单的名字代替一个长的字符串,因此把这个标识符称为“
宏名
”。
#define
宏名
字符串
;
在预编译时将宏名替换成字符串的过程称为“
宏展开
”。3.7.1宏定义第七十九页,共九十四页,2022年,8月28日#define
PI3.1415926 /*PI是宏名,3.1415926用来替换宏名的常数main(){floatradius,length,area,volume;cin>>radius;length=2*PI*radius; /*引用无参宏名求周长*/
area=PI*radius*radius;/*引用无参宏名求面积*/
cout<<length<<area;}length=2*3.1415926
*radius;例3.20输入圆的半径,求圆的周长、面积。要求使用无参宏定义圆周率。第八十页,共九十四页,2022年,8月28日(1)可提高源程序的可维护性(2)可提高源程序的可移植性
(3)减少源程序中重复书写字符串的工作量使用宏定义的优点第八十一页,共九十四页,2022年,8月28日说明:(1)在定义宏时,“宏名”和“字符串”之间要用空格分开。而“字符串”中的内容不要有空格。(2)宏名通常用大写字母定义。(3)宏定义是在编译前对宏进行替换的,但对程序中用双引号括起来的字符串内容,如果其中有与宏名相同的部分,是不进行文本替换的。例如:#include<iostream.h>#definePI3.14159
voidmain(){cout<<"PI="<<PI<<endl;}程序运行结果如下:PI=3.14159第八十二页,共九十四页,2022年,8月28日(4)宏定义后,可以在本文件各个函数中使用。也可以使用#undef取消宏的定义。(5)宏定义可以嵌套,已被定义的宏可以用来定义新的宏。例如:#include<iostream>#defineM3#defineNM+2//用已定义的宏M来定义新的宏N#defineL4*N+6//用已定义的宏N来定义新的宏Lvoidmain(){cout<<L<<endl;}
程序运行结果如下:20与常变量的区别
const
double
PI3.14159第八十三页,共九十四页,2022年,8月28日2.有参数宏定义
带参数宏定义的一般格式
#define宏名(形参表)字符串
宏展开:不只是简单地将宏名用字符串替换
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 专业版股权质押权责明确协议样本一
- 科技驱动未来
- 元宵节数字营销解读
- 2025年度拆除工程噪音污染控制合同4篇
- 2025年度厂房设备租赁与绿色制造合同范本4篇
- 《中科院化学课件:不对称催化反应及其在药物合成中的应用》
- 二零二五年度腻子材料批发与零售合同3篇
- 2025年度厂区装卸工劳动保障政策宣传合同4篇
- 2025年度绿色环保型老旧厂房拆除及重建一体化工程合同4篇
- 2025年度高端医疗器械研发与生产合同4篇
- 平面向量及其应用试题及答案
- 2024高考复习必背英语词汇3500单词
- 消防控制室值班服务人员培训方案
- 《贵州旅游介绍》课件2
- 2024年中职单招(护理)专业综合知识考试题库(含答案)
- 无人机应用平台实施方案
- 挪用公款还款协议书范本
- 事业单位工作人员年度考核登记表(医生个人总结)
- 盾构隧道施工数字化与智能化系统集成
- 【企业盈利能力探析文献综述2400字】
- 2019年医养结合项目商业计划书
评论
0/150
提交评论