欢迎来到三一办公! | 帮助中心 三一办公31ppt.com(应用文档模板下载平台)
三一办公
全部分类
  • 办公文档>
  • PPT模板>
  • 建筑/施工/环境>
  • 毕业设计>
  • 工程图纸>
  • 教育教学>
  • 素材源码>
  • 生活休闲>
  • 临时分类>
  • ImageVerifierCode 换一换
    首页 三一办公 > 资源分类 > PPT文档下载  

    结构化程序设计的基本思想是围绕系统功能自顶向下.ppt

    • 资源ID:6332374       资源大小:861.50KB        全文页数:101页
    • 资源格式: PPT        下载积分:15金币
    快捷下载 游客一键下载
    会员登录下载
    三方登录下载: 微信开放平台登录 QQ登录  
    下载资源需要15金币
    邮箱/手机:
    温馨提示:
    用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)
    支付方式: 支付宝    微信支付   
    验证码:   换一换

    加入VIP免费专享
     
    账号:
    密码:
    验证码:   换一换
      忘记密码?
        
    友情提示
    2、PDF文件下载后,可能会被浏览器默认打开,此种情况可以点击浏览器菜单,保存网页到桌面,就可以正常下载了。
    3、本站不支持迅雷下载,请使用电脑自带的IE浏览器,或者360浏览器、谷歌浏览器下载即可。
    4、本站资源下载后的文档和图纸-无水印,预览文档经过压缩,下载后原文更清晰。
    5、试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。

    结构化程序设计的基本思想是围绕系统功能自顶向下.ppt

    第四章 函数,结构化程序设计的基本思想是:围绕系统功能自顶向下,逐步细化和精化。,主要内容:,函数的概念;,递归的概念和递归函数;,函数的声明(定义)和调用;,细化过程:对系统功能逐层分解出许多易于理解和实现的、逻辑上相对独立的子功能,形成一棵功能树。,在程序实现时,功能树上的每一个结点,对应一个函数。,重点:,函数的声明和函数调用;,递归概念及其应用;,数据的存储类别和作用域。,课堂时数:1012学时,上机时数:45学时,课外上机时数:56学时,数据的存储类别和作用域。,内联函数和重载函数;,4.1 函数的基本概念,我们从结构化程序设计的角度来讨论大程序的功能分解及其函数实现,进而给出函数的定义并讨论函数的作用。,1什么叫函数,把实现特定功能的相关数据和语句组织在一起,并给出相应的名称、类型和参数集,成为程序的一个可调用部分和可共享部分,这种组合形式称为函数。,1)分析,我们将新建、打开、关闭、打印等文件处理功能描述成四个相对独立的函数,分别是:,例4-1 文件处理包括以下几个功能:新建、打开、关闭、打印,请给出有关文件处理的程序框架。,new()、open()、close()、print(),这些函数都包含了各自的数据和语句,并且都有各自的外部名称函数名。,/程序头部void main();new();open();close();print(),2)程序框架,3)调用结构,main函数分别调用了new、open、close、print四个函数。,注意:一个C+程序可以包含多个函数,这些函数在定义层次上必须是相同的,但调用层次却可以不相同。,2函数的作用,函数是特定子功能的实现,也即将大程序分割成若干个小程序(子程序)。每个子程序就是一个函数,这样既便于理解也便于实现。,当函数所实现的是程序中频繁使用的一些公共操作,那么该函数就可以重复调用,也就是所谓的“代码重用”。,若干功能相关的函数可以组成一个模块。,综上所述,合理地使用函数便于功能分解和程序实现;可以减少程序的重复编写;可以使程序结构清晰、增强易读性。这就是函数的三大作用。,3函数的分类,通常分为:标准函数、用户自定义函数、类的成员函数三大类。,标准函数也称为库函数,是系统定义的函数,c+提供了种类繁多的库函数,具体可以分为:,I/O类,处理I/O操作;,文件类,处理二进制文件和缓冲型文件;,字符串类,实现字符串运算和字符串操作;,内存类,处理内存分配、释放、测试,传送等工作;,系统类,实现系统调用、系统设置以及底层硬件操作等。,4.2 函数定义,程序中使用函数就象使用数据对象一样,必须先定义后使用。定义函数也称为声明函数。,1作用,给出函数的名称、类型、参数,描述函数内部的数据和语句。,2语法,C+的函数由函数头部(函数原型)和函数体两部分组成,其语法描述为:,()数据说明部分;,/函数头部,解释:,类型标识符:用来说明函数的数据类型,也即函数调用后所返回的调用结果的数据类型,可以是int、char、float、double、void等。如果省略类型标识符的话,则缺省类型是int。如果该函数的类型无关紧要,一般表示成void。,例如:void main(),double max(double x,double y),main的类型是void而max的类型是double。,函数名:C+用标识符作为函数名。函数名具有变量特征,它用来存放返回值。,例如:sqrt(16)sqrt=4;upper(a)upper=A;,参数表:用来说明函数的参数,函数的参数是函数所提供的数据接口,用来与调用者传递数据。因此参数说明必须给出参数的名称、类型。允许一个函数包含0到多个参数,参数之间用逗号分隔。,数据说明部分:位于函数体内,用来说明函数内部所用到的数据对象(变量等),如果函数没有内部数据,则此部分可以省略。,函数说明所定义的参数称为“形式参数”,而函数调用所提供的参数称为“实在参数”。,例如:char upper(char c)只有一个参数,名称为c,类型是字符型。double max(double x,double y)有两个参数x和y,类型都是双精度型。,执行部分,位于函数体内,由若干可执行语句组成,该部分不能省略。,例4-2 编写一个将小写英文字母转换成大写英文字母的函数。,char upper(char c)char c1;if(c=a,函数名:upper,类型:字符型;,形式参数:char c;,内部数据说明:char c1;,执行部分:if语句和return函数调用。,例4-3 编写计算字符串长度的函数strlen,int strlen(char s)int i=0;while(si!=0)i+;return(i);,函数名:strlen,类型:整型;,形式参数:char s;s是数组,内部数据说明:int i;,4.3 函数原型,函数原型是一条特殊的说明语句,用来说明程序中所引用的函数的名称、函数返回值类型和参数类型。,1什么叫函数原型,函数原型格式上与函数定义的头部大致相同,但因为是独立的语句,必须以分号结束,并且一般放在程序的顶部。,例4-4 在引用strlen函数的程序中加上其原型说明。,#include int strlen(char*);main()strlen();int strlen(char s),strlen函数的原型说明,2函数原型的语法,函数原型的格式与函数定义的头部格式大同小异,其语法如下:,();,注意:,(1)参数类型表给出函数所有参数的数据类型(无须给出参数名);,(2)函数原型和函数定义在函数类型(返回值类型)、函数名以及参数表这三部分上必须一致,否则就会发生编译错误;,(3)标准库函数的原型都在对应的头文件中(*.h)。如果程序中使用到某些标准库函数,必须在程序顶部,使用#include命令将对应的文件包含进来。,3函数原型的作用,在程序中加入函数原型,有利于保证函数定义和调用上的一致性。这里的一致性是指定义和调用时,函数类型、参数个数和类型方面的一一对应。,一致性的检测工作是由编译系统自动进行的,当程序中加入函数原型,一旦函数原型不正确,也即与函数定义不同,编译系统会报告出错,并给出相应的出错信息。,例如在例4-4的main函数中按以下格式调用strlen函数:strlen(s1,n);,编译程序在编译过程会检测到在调用strlen函数时提供的参数过多的错误,并提示:function does not take 2 parameters(函数在调用中遇到过多的函数参数),void func1(int,float);void main()int a;float b;cin a b;func1(a,b);void func1(int,int)float c;c=a+b;,例4-5 函数原型和函数定义其参数类型不一致的问题示例。,分析:,(1)程序中,func1函数原型和函数调用是一致的,能正确地通过编译。,注意:当所调用的函数是标准函数或该函数不在本程序中,定义和调用的一致性问题就更加重要了。,函数的功能是通过调用实现的,调用时必须提供与函数定义相对应的各个参数的实际值,调用的返回值存放在函数名中,因此函数调用结果可以视为一个数据对象。,4.4 函数调用,将函数调用视为数据对象并作为表达式的成员参加运算,这是函数调用的一般形式。,1函数调用的格式(语法),();,解释:,(1)函数是“按名调用”的,所以函数名必须准确无误,尤其是在调用标准库函数时,一定要保证函数名一字无误。,例如:len=strlen(s);sin(x)/con(x);,例如:要调用scanf()函数,如果将函数名写成scan,便会出现“不能确定的外部函数”这样的错误。此类错误是初学者最容易犯的。,(2)参数表:给出与函数定义所描述的形式参数相对应的各个实在参数。所谓相对应是指:类型一致,个数相等,顺序吻合。,/strlen:函数名,s:参数,(3)函数调用时所提供的实参其形式可以是常量、变量、表达式、函数调用等。但其值必须是确定的。请看以下一些函数调用:,2函数调用的形式,sqrt(30)sqrt(a)sqrt(a*100)sqrt(abs(x),前面已提到函数调用的一般形式是将其视为数据对象,在参加运算时实现调用。这种数据对象可以出现在以下三种场合,分别对应三种不同的调用形式:,实参是常量;,实参是变量;,实参是表达式;,实参是另一函数调用。,1)表达式,函数调用出现在表达式中,参加所在表达式的运算。,例如:2*sqrt(56),2)函数调用语句,函数调用语句实际上是仅含单个“函数调用数据对象”的特殊表达式,因此是1)的特例。,例如:scanf(),printf()等。,3)函数参数,函数调用所提供的实参是某一函数调用,这种情况称为函数参数。,3函数调用机制和调用过程,例如:max(a,max(b,c)sqrt(abs(x)。,设程序p1具有三个函数,main(),f1(),f2(),其调用关系是main函数调用f1函数,f1函数调用f2函数,程序如下:,void main()int a,b;f1(a);,/main调用f1,p1程序的执行过程如下图所示:,void f1(int x1)char c f2(c);void f2(char x2)float d,/f1调用f2,分析程序的执行过程,可以发现以下特征:,(1)main最先执行、最后结束,f2最后执行却最先结束,正所谓“先进后出”;,main,f1,f2,由f2返回f1,由f1返回main,(2)每一函数在调用另一函数时必须暂时中断自身的执行;,(3)每一个被调用的函数执行结束后,必须能返回主调函数调用处的下一执行点,也即调用前必须保留返回地址。,能够满足以上要求的函数调用机制可以概括为:中断+运行栈,也即c+借助于中断机制和栈来实现函数调用。,1)中断,中断是指主调函数暂时中止自身的运行,转入被调函数的执行部分,待被调用函数运行结束,主调函数又开始余下部分的执行。,2)运行栈,栈是一种“先进后出”的数据结构,也即先压进去的数据最后弹出。C+在处理函数调用时,所使用的栈称为运行栈。,运行栈用来存放返回地址、函数参数以及函数内部定义的数据等。运行栈随着程序的运行而动态变化。,程序p1当运行至的f2被调用时的运行栈如下图所示。,3)调用过程,(1)在运行栈的栈顶建立被调函数的栈空间;,每一个函数的调用过程都包括:调用初始化,执行,善后以及返回三个阶段,具体分为以下步骤:,(2)保护主调函数的运行状态和返回地址(保存在栈区内);,(3)传递参数;,(4)将运行控制权交给被调函数;,(5)被调函数的执行;,(6)恢复主调函数的运行状态;,(7)取出返回地址;,(8)返回主调函数。,执行,4)参数传递,可以看出(1)(4)属于初始化阶段、(5)是执行阶段、(6)(8)是善后阶段。,所谓参数传递就是将实参存入函数栈空间的参数区,用来代替函数定义所描述的形式参数。,(1)参数的传递顺序,按参数表中参数的排列顺序自左向右逐一传递。,例如:函数调用f1(a,b,c),其参数的传递顺序是:先传递a再传递b最后传递c。,(2)参数的传递方式,将实参的值存入对应形参的参数区中。实参可以是变量、常量、表达式。,a.传值,有三种传递方式:,注意:此种方式,被调函数对参数的修改不会影响主调函数用做实参的数据对象。,换句话说,参数的此种传递方式,只能实现数据的单向传递,也即无法由实参带回函数对参数的任何修改。,b.传地址,所谓传地址是将实参的地址存入对应形参的参数区中。实参可以是变量、数组。,传地址与传值的不同之处在于:传递的不是实参的值而是实参这一数据对象的内存地址。,简而言之,这样的参数传递方式,可以实现数据的双向传递,也即可以由实参带回函数对参数的任何修改。,引入这种传递方式目得在于“使得被调函数对参数的修改能够影响主调函数用做实参的数据对象”,并且可以减少数据的传送量。,例:以借书为例,将书直接借给你传值;告诉你借书的地方传地址。,例4-6 试问在下面程序段中,调用函数f1后,a,b,c的值分别是什么?,int a=1,b,c;c=f1(a,分析:,(1)参数a采用传值方式,参数b采用传地址方式。&b表示取b的地址。,(2)调用函数参数f1后,a,b,c的值分别是:a=1,b=10,c=0。为什么?,注意:f1虽然修改了a却无法返回给主调函数,因为a是值参。,c.传名,5)实例,下面我们给出两个函数应用的实例,来进一步阐明函数的定义和调用过程。,例4-7 编写一个程序。分别求两个字符串s1和s2的长度。,(1)分析,我们可以在例4-3所编写的strlen函数的基础上增加一个主函数,该函数两次调用strlen函数。,(2)程序,函数的参数是函数指针,对应的参数必须是函数名。,#include int strlen(char*);void main()int len;len=strlen(abcdefg);cout s1的长度是:len endl;len=strlen(aaa123456);cout s2的长度是:len endl;int strlen(char s)int i=0;while(si!=0)i+;return(i);,/第2次调用strlen函数,/第1次调用strlen函数,1)分析:,我们可以在例4-2所编写的upper函数的基础上增加一个主函数,该函数循环调用upper函数。,例4-8 编写一个程序,用来将输入的字符串s1中的小写字母转换成大写字母。,2)程序:,#include int strlen(char*);char upper(char);void main()char s30;int n.i;cout s;,n=strlen(s);for(i=0;i=a;,/循环调用upper函数,/调用strlen函数,计算串长,4函数的嵌套调用,虽然C+不允许在函数内部定义新的函数(嵌套定义),但却允许函数内部调用另一函数,也即允许函数之间相互调用,这种调用形式称为嵌套调用。,/strlen int strlen(char s)int i=0;while(si!=0)i+;return(i);,通过嵌套调用动态地建立函数之间的关系,使得程序的层次化的功能结构得以实现。,1)概念,所谓函数的嵌套调用是指:在函数的执行部分包含对一个或一些函数的调用。,嵌套可以是单重的也可以是多重的,如果被调函数的执行部分不再出现对另一函数的调用,就是单重嵌套。相反,如果被调函数的执行部分也包含对另一函数的调用,就构成多重嵌套。,注意:将函数调用作为实参,不能视为嵌套调用。,例如sin(abs(x),不是sin函数调用abs函数,因为abs函数的调用并没有出现在sin函数的执行部分中。相反是在参数传递的时候就实现的。,2)实例,例4-8所给的程序是函数的单重嵌套调用,并且是并列单重调用。下面分析其调用关系和调用过程。,调用关系:main函数调用strlen函数和upper函数,注意strlen和upper是并列调用。,调用过程:,main,strlen,upper,void main()int a,b f1(a);void f1(int x1)char c f2(c);void f2(char x2)float d,下面的程序包含着函数的多重嵌套调用,main函数调用f1函数,而f1函数又调用f2函数。,4.5 递归函数,在客观世界中,很多问题的处理方法是递归的,例如“折半查找”问题,其处理方法描述为:,(1)取n个有序数据正中间的一个数据dm与查找关键字Key比较,如果dmkey,则可以确定接下去的查找范围是m+1n之间;如果dm=key,则查到所须对象,查找结束。,该方法告诉我们,在查找范围不断变化的情况下,后一步可以使用与前一步相同的方法来进行查找。这种现象是递归调用现象后一步骤调用前一步骤所使用的方法。,(2)用新的查找范围替代上一次的查找范围,重复(1)。,再比如阶乘问题,n!=n*(n-1)*(n-2)*1,这种递推问题也可以用递归调用来实现,即:n!=n*(n-1)!,例4-9 计算阶乘 n!,#include long fact(int);void main();long fact(int n)if(n=1)return(1);else return(fact(n-1)*n);,/在fact函数中调用fact函数递归调用,void main()long result;int n;cout n;if(n=1)result=fact(n);cout endl n!=result endl;else cout endl 输入的数据不正确!endl;,fact函数的执行部分,出现自身的直接调用直接递归调用。,调用过程中,提供给fact的实参按:n,n-1,n-2,,1的规律递减;,调用的返回值却按:1,2*1,3*2*1,(n)*(n-1)*1的规律变化。,设n=3,则上述数据在调用与返回过程中的变化情况如下图所示:,1递归的概念,注意:程序设计语言通常只支持递归调用,C+也不例外。,实体的定义(属性)或实体的处理(操作)包含着实体自身,这种现象称为递归,前者称为递归定义,后者称为递归调用。,比如,例4-9中,fact函数的执行部分包含对自身的调用。,2递归函数的概念,我们把函数的执行部分出现另一函数调用这种现象称为函数的嵌套调用,如果所出现的函数调用是自身的调用,这种特殊的嵌套形式就是函数的递归调用函数直接或间接地调用自身。,3递归调用的基本形式,所谓递归函数即自调用函数,也即在函数体的执行部分中出现函数自身的直接或间接调用。可以看出,递归调用是嵌套调用的特例。,分为直接递归调用和间接递归调用两种形式:,直接递归调用:函数体的执行部分出现本函数的调用。这种调用方式较容易理解。,间接递归调用:其函数体内并没有出现自身的调用,而是出现在被调函数的函数体的执行部分中。请看下面的例子:,4递归调用的条件,运用递归调用技术必须满足以下三个基本条件:,f1()f2();f2()f1();,分析:,1)f1调用f2;,2)f2调用f1;,3)f1间接调用自身;,注意:此种形式较为隐蔽,容易造成误解或疏忽。,1)可以把一个问题转化为一个新的问题,新问题的解决方法必须与原问题的解决方法相同,,2)可以将问题的解决方法描述成相对独立的功能(过程化),以便于用函数来实现。,例如:折半查找问题其查找范围,有规律地减小;阶乘问题,其乘数按n,n-1,n-2,1规律递减。,只是所处理的对象有所不同,但所处理的对象其属性的变化必须是有规律的。,3)递归函数中必须有确定的结束递归的条件,并且应该遵循先测试后调用的原则。,如果函数中没有确定的结束递归的条件,将会造成无限递归。如果不遵循先测试后调用的原则,那么递归结束条件形同虚设。,5.递归函数应用实例,hanoi塔问题是递归方法的典型应用实例,讨论该问题的目的在于帮助大家在分析和解决实际问题时如何发现递归特征,并建立递归模型,以简化程序设计。,例4-10 编写一个含有递归调用的程序,处理hanoi塔问题。,hanoi问题描述为:,有三根细柱A,B,C,A柱上套有64个盘子,这些盘子大的在下,小的在上。要求将64个盘子从A柱移到C柱,移动过程中可以借助B柱,但每次只允许移动一个盘子,并且任何时候,任何柱子的盘都必须保持大盘在下,小盘在上。,(1)移动过程分析:,我们以柱上只有三个盘的情况来分析并观察移动过程:,2)移动方法(步骤):,(1)将A柱上的n-1个盘子借助C柱先移到B柱上;见图4-8(2)4-8(4)。,(2)将A柱上剩下的一个盘子移到C柱上;见图4-8(5),(3)将B柱上的n-1个盘子借助A柱移到C柱上。见图4-8(6)4-8(8)。,可以看出步骤(3)和步骤(1)采用的方法是相同的,只不过是盘子数目减少一个,并且源柱、工作柱和目标柱也发生变化:,步骤(1)的源柱是A,工作柱是C,目标柱是B;而步骤(3)其源柱改为B,工作柱改为A,目标柱是C。,显然这是一个递归问题,下面给出对应的程序:,#include void hanoi(int,char,char,char);void move(char,char);main()int m;cout m;cout The setp to moving m diskes:n;hanoi(m,A,B,C);,/调用移动函数,/输入盘子数,void hanoi(int n,char one,char two,char three)if(n=1)move(one,three);else hanoi(n-1,one,three,two);move(one,three);hanoi(n-1,two,one,three);void move(char getone,char putone)cout getone putone endl;,/递归调用hanoi函数,输入:3,输出:?,AC AB CB AC BA BC AC,4)程序执行过程分析,6.消除递归,递归可以简化程序设计,但因为需要频繁地调用函数,所以必然会影响程序效率,因此在某些特别强调程序效率的场合,可以考虑用别种结构来代替递归结构,这就是所谓的“消除递归”。,从控制结构来看,函数的递归调用实际上就是函数的重复调用,它导致函数执行体的重复执行,这与循环极为相似。因此大多数的递归问题可以转化为循环问题,这是程序设计中消去递归常用的方法。,那么何种结构可以代替递归结构?,我们不妨回到本节一开始就提到的计算阶乘问题这一例子上:,程序(函数):,例4-11 用循环语句代替递归函数调用,求解n!。,#include double fact(int);double fact(int n)int i;double f=1;for(i=n;i=1;i-)f=f*i;return(f);,注意:用循环结构消去递归,是有局限性的,也即只能消去只存在着动作重复这一类递归问题,如果连数据也存在重复定义,这样一类问题,就无法用循环去取代了,例如hanoi塔问题就是一个例证。,7递归的评价,采用递归技术的目的是简化程序设计、增强程序的易读性,但函数的递归调用明显会增加系统开销,包括系统空间的开销和运行时间的开销都会增大。,在非常讲究程序执行效率的场合,应尽量减少使用递归编程技术;相反,在强调程序结构、程序的易读性和可维护性的场合,却鼓励使用递归程序设计技术。,4.6 内联函数,1 内联函数的引入,在问题的分析与分解过程中,往往会得到一些处理和逻辑结构都相对简单的“小功能”,例如出错提示之类的功能,既相对独立又相对简单。,#include#include void sread(char*);void main()char s30;int n,i;,例4-12 将例4-8的程序改写如下:,对于这些“小功能”是否有必要设置为函数了?请比较下面的两个例子:,sread(s)n=strlen(s);for(i=0;i=a,分析:,程序所作的改动有以下两处:,/输入字符串,(1)设置sread函数实现字符串输入操作,对于本程序而言只有单一数据输入,所以这样设置反而有画蛇添足之嫌。,(2)取消upper函数,将其处理部分直接嵌入到main的for语句中,目的在于提高程序的执行效率。,for(i=0;i=n-1;i+)si=upper(si);,for(i=0;i=a,这样的修改显然破坏了upper功能的独立性,因为它现在必须依附于具体的处理而存在了。,问题:,我们引入内联函数来解决这个两难的问题。,试问如果程序中出现多处的Upper处理,程序结构上又会出现什么问题了?,至少其简洁性,结构的清晰性和程序的可读性和可维护性等都会受到影响。,#include#include inline char upper(char);void main()char s30;int n,i;cout s;,请看下面的程序:,n=strlen(s);for(i=0;i=a;,upper函数被说明成inline的(内联),其实际意义是:编译程序将会为upper创建一段代码,程序中凡是出现upper的调用,都用该段代码来替换。,2内联函数的声明,在函数的原型说明和函数的定义处都加上inline,其格式如下:,函数原型:inline();,函数定义:inline(),注意:内联函数必须在调用之前声明或定义,否则编译系统无法识别,仍会当成一般函数处理。,3内联函数的几点限制,(1)函数体内不能出现复杂的结构语句,例如switch和while等。如果出现了上述语句,则内联定义无效,仍视为一般函数。,4内联函数的应用实例,(2)递归函数不能定义成内联函数。,(3)内联函数一般适用于小函数(含15行代码)。,例4-14 下面的函数用来输入各个数据项目,并检查其正确性,如果输入不正确的数据,则提示出错,并设置相应的错误代码。,实现方式1:,DataInput()char name20;int age,grade,ErrorCode=0;char sex;,cout name;cout sex;if(sex!=M,函数中因为多处出现输入出错提示和出错代码设置,我们可以将其定义为内联函数,请看实现方式2:,inline error(char*,int);DataInput()char name20;int age,grade,ErrorCode=0;char sex;cout name;cout sex;if(sex!=M,cout age;if(age30)error(年龄输入不正确!,2);cout grade;if(grade100)ErrorCode=error(成绩输入不正确!,3);return(ErrorCode);inline error(char*ErrorMessage,int ErrorCode)cout endl 性别输入不正确!endl;return ErrorCode;,4.7 重载函数,1 什么叫重载,这里的重载是指对运算的再定义。分为函数重载和运算符重载两种形式。,“+”运算符可以用来进行整数、实数、指针,甚至结构等各种不同类型数据的加法运算;(2)cin、cout操作符能适应不同类型数据的输入输出;(3)abs函数可以对不同类型的数值数据求绝对值。,2什么叫重载函数,对多个功能相同,但参数类型或函数返回值类型不同的函数使用同一个函数名,这样的函数称为重载函数。,例如对数值数据求绝对值,如果考虑整型、浮点型和长整形三种不同类型的数据。则应该有三个求绝对值函数:,int abs(int);float fabs(float);long labs(long);,事实上我们可以将上述三个函数表示成重载函数:,int abs(int);float abs(float);long abs(long);,尽管还需定义三个函数:但用的却是同一个名称abs。这样当然给使用者带来极大的方便。,例4-15 将abs定义成重载函数。,#include int abs(int);float abs(float);long abs(long);void main();int abs(int x)if(x0)return(-x);else return(x);,float abs(float x)if(x0)return(-x);else return(x);long abs(long x)if(x0)return(-x);else return(x);,C+编译系统能对每一个重载函数的调用实现正确的匹配,例如:,void main()cout abs(-9)endl;cout abs(-100.9f)endl;cout abs(-10l)endl;,abs(-9)int abs(int);abs(-100.9f)float abs(float);abs(-10l)long abs(long)。,3匹配重载函数的顺序,如前所述,c+能对每一个重载函数的调用实现正确的匹配,其依据是什么了?,1)匹配方法,将实参类型和被定义成重载的各个函数的形参类型一一比较,选择参数类型一致或相容的函数,并调用之。,例如:当出现函数调用 abs(100.9)时,编译系统先用100.9去匹配 abs(int),显然不是合适的匹配,接着匹配abs(float),因为参数类型相同,找到合适的匹配,也即找到所要调用的函数。,2)匹配过程,(1)首先寻找一个严格的匹配,如果找到了,就调用该函数。,所谓严格匹配是指实参和重载函数形参的类型完全一致。,(2)通过内部转换寻求一个匹配,只要找到了,就调用该函数。这种转换指的是“相容”类型的转换。,例如从int到long或从int到double的转换都是相容的。,(3)通过用户定义的转换寻求一个匹配,若能查出有唯一的一组转换,就调用该函数。,4.8 全局变量和局部变量,这一小节简要地讨论数据作用域和程序结构以及编译预处理等问题。,1 C+程序运行时的内存区域,一个C+程序在运行时,由操作系统为其分配内存空间,该空间由以下四部分组成:,(1)代码区:存放程序代码(机器指令码);,(2)全局数据区,存放程序中的全局数据和静态数据(声明为static的数据对象);,(3)堆区,存放程序的动态数据,通过动态申请内存空间操作获得(将在第六章介绍);,(4)栈区,运行栈空间(参见)。,C+程序程序运行时的内存空间示意图,2变量作用域,所谓变量作用域,就是变量的有效范围,分为全局和局部两种形式。,全局作用域:是程序全程可知的、有效的。外部变量具有全局作用域。,局部作用域:在函数范围内是可知的、有效的,例如函数内部声明的变量,仅在本函数内部有效。,3全局变量,(1)什么叫全局变量,在所有函数之外声明的变量称为全局变量,其作用域是全局的,也即在整个程序运行期间有效,程序中的每一个函数均可以引用之。,请看下面的程序:,#include int n;char ret=t;void f1(int);void main()int v1,v2;,cin n v1;v2=f1(v1);if(ret!=f)cout v2 n;void f1(int x)n=n+x;ret=f;,main函数通过cin表达式给n赋值,f1函数通过赋值表达式n=n+x既引用n值又给n赋值;f1函数通过赋值表达式ret=f对ret赋值,而main函数则引用ret。,(2)全局变量的作用,全局变量有以下两个主要用处:,用来定义系统的全程数据,例如环境参数,系统运行状态和标记等,这些数据一般都与多个函数有关。,增加函数之间数据联系的渠道,也即用全局量代替函数参数。,全局变量对每个函数是可知的,因此如果某一函数修改了某一全局变量的值,都会影响到引用该变量的其它函数,实际上这些函数通过全局变量建立了一种隐式的数据联系。,下面的例子告诉我们如何用全局变量代替函数参数,建立函数之间的一种隐式的数据联系。,例4-16 用全局变量代替函数参数。,#include int max();int a,b;main()cin a b;cout b?a:b;return z;,分析:,全局变量a,b用来建立main和max的联系,也即用全局变量a,b代替max函数的参数x,y。对于max函数而言,a,b不再是任意的了。,注意:,(3)有关全局量的几点说明,用全局量作为函数数据联系的渠道,虽然可以简化函数参数,但函数的独立性弱化了,必须依赖于具体程序。而且隐式的数据联系可能是程序员可知的也可能是程序员未知的。如果是未知的话就可能带有程序的潜在错误。,数据参数化后增强了函数的独立性,也即函数有了属于自己的属性。这样的函数具有通用性,与具体程序无关。,全局变量存放在程序内存空间中的全局数据区,编译程序会自动对全局变量初始化。,如果全局变量a的定义出现在某些函数之后,那么a是这些函数非直接可知的。也即这些函数不能直接引用a。,如果要引用的话,必须在函数体内加上外部变量说明:extern a;,例4-17 我们对例4-16的程序修改如下:,#include int max();main()cin a b;cout max();,编译过程中,会出现main中的a,b未声明的错误。因为全局量a,b是在main函数之后定义的。为了正确引用,必须在main中增加外部变量说明:extern a,b;,一个源程序模块中定义的全局变量,允许被同一项目中的其他源程序模块中的函数所引用,但必须加上外部说明。(extern),int a,b;int max()int z;z=ab?a:b;return z;,/定义全局变量a,b,4局部变量,函数内部定义的变量称为局部变量,或自动变量(auto)。,局部变量在函数栈区中分配空间,仅在函数内部有效。,注意:,局部变量的存储类别是auto,但在定义变量时一般省略该存储类别说明。例如函数中定义 auto int a 和定义 int a是等价的。,局部变量在定义时没有初始化,其初始值是不可预料的。,同一程序的不同函数可以定义同名局部量,它们代表的是不同的数据对象。,如果全局量与某个函数的局部量同名,则全局量在该函数中失效(被同名局部量屏蔽了)。,f1()int a,b;f2()int a;char b;,f1的a,b有别于f2的a,b。,例如:,例如:,include int a=10;main()int a,b;a=5;cout a=a;,分析:,(1)全局变量a和main函数的局部变量a同名,所以它在main函数中被屏蔽。,(2)main函数的cout操作输出5而不是输出10。,4.9 变量的存储类别,1C+存储类别概述,所谓存储类别是指变量的存储形式,包括存储位置和生存期,分为全局(外部)、局部(自动)、静态、动态四种形式:,外部存储类别(extern),自动存储类别(auto)和寄存器存储类别(register),静态存储类别(static),2外部存储类别,外部存储类别用来声明外部变量和外部函数,被声明的对象必须是已经定义过的。,注意:,(1)请区分“声明”和“定义”的区别。,(2)存储类别符是extern。,(3)有两种场合需要使用外部存储类别声明全局变量:,在一个源程序中,全局变量在定义之前引用,或者说,引用全局变量的函数出现在该全局变量的定义之前,那么必须在函数内部将全局变量声明为“外部”的(参见例4-17)。,一个程序由多个源文件组成,假设源文件p1引用的全局量或调用的函数在其它源文件中定义,则p1中必须将这些变量或函数声明成“外部”的。,例4-18 下面的程序包含多个源程序文件,我们称为多文件结构,本例主要讨论各个源程序之间关于全局数据对象和函数的相互引用问题。,/p1.cpp#include void f1();extren void f2();int n;void main()n=3;f1();cout n;,/该函数在p2.cpp中定义,void f1()f2();/p2.cpp#include void f2();extern int n;void f2()int c;cin c;n=c*8;,/n在p1.cpp中定义,分析:,p1中引用了p2定义的f2,所以f2在p1中声明为:extern。,注意:,函数的作用域总是全局有效的。,P2中引用了p1中定义的全局变量n,所以声明为:extern。,3静态存储类别,静态存储类别可以用来修饰全局量、局部量和函数,分别称为静态全局变量、静态局部变量和静态函数。,(1)静态存储类别标识符是static。,(2)为什么要定义静态全局变量,一旦全局变量被声明成静态的,则该变量由定义它的源程序文件所独占,不能被其它源程序文件引用。,(3)为什么要定义静态局部变量,换句话说,静态全局变量对组成该程序的其它源文件是无效的。这样可以避免全局变量同名所造成的干扰。,某一局部变量被声明成静态的,则该变量的值可以被保留,不会因为函数调用结束而消失(为什么?)。,例4-18 试比较

    注意事项

    本文(结构化程序设计的基本思想是围绕系统功能自顶向下.ppt)为本站会员(小飞机)主动上传,三一办公仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知三一办公(点击联系客服),我们立即给予删除!

    温馨提示:如果因为网速或其他原因下载失败请重新下载,重复下载不扣分。




    备案号:宁ICP备20000045号-2

    经营许可证:宁B2-20210002

    宁公网安备 64010402000987号

    三一办公
    收起
    展开