第4章 过程抽象——函数_第1页
第4章 过程抽象——函数_第2页
第4章 过程抽象——函数_第3页
第4章 过程抽象——函数_第4页
第4章 过程抽象——函数_第5页
已阅读5页,还剩81页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第四章功能(过程)抽象函数,主讲人:侯海良通信与控制工程系,本章内容,基于过程抽象的程序设计子程序的概念C+的函数变量的局部性和变量的生存期标识符的作用域递归函数内联函数函数名重载条件编译程序调试与多环境程序编制标准库函数,基于过程抽象的程序设计,人们在设计一个复杂的程序时,经常会用到功能分解和复合两种手段:功能分解:在进行程序设计时,首先把程序的功能分解成若干子功能,每个子功能又可以分解成若干子功能,等等,从而形成了一种自顶向下(top-down)、逐步精化(step-wise)的设计过程。功能复合:把已有的(子)功能逐步组合成更大的(子)功能,从而形成一种自底向上(bottom-up)的设计过程。过程抽象:一个功能的使用者只需要知道相应功能是什么(whattodo),而不必知道它是如何做(howtodo)的。,子程序,子程序是取了名的一段程序代码,在程序中通过名字来使用(调用)它们。子程序的作用:减少重复代码,节省劳动力实现过程抽象(功能抽象)封装和信息隐藏的作用,子程序之间的数据传递,一个子程序所需要的数据往往要从调用者(也是一个子程序)那里获得,计算结果也需要返回给调用者。子程序之间的数据传递方式可以通过:全局变量:所有子程序都能访问到的变量。(不好)参数:形式参数(形参)和实在参数(实参)。值传递:把实参的值复制一份给形参。地址或引用传递:把实参的地址传给形参。返回值机制:返回计算结果。,C+函数,函数是C+提供的用于实现子程序的语言成分。函数的定义:()描述了函数返回值的类型,可以为任意的C+数据类型。当返回值类型为void时,它表示函数没有返回值。用于标识函数的名字,用标识符表示。描述函数的形式参数,由零个、一个或多个形参说明(用逗号隔开)构成,形参说明的格式为:,为一个,用于实现相应函数的功能。函数体内可以包含return语句,格式为:return;return;当函数体执行到return语句时,函数立即返回到调用者。如果有返回值,则把返回值带回给调用者。如果return中的的类型与函数不一致,则进行隐式类型转换,基本原则为:把转成。注意:在函数体中不能用goto语句转出函数体。,例1:用函数实现阶乘,intfactorial(intn)/求n的阶乘inti,f=1;for(i=2;i=0)while(n0)product*=x;n-;elsewhile(nx;coutFactorialofxisfactorial(x)/调用阶乘函数b;couta的b次方是:power(a,b)n;/从键盘输入一个正整数if(n2)return-1;cout2,;/输出第一个素数for(i=3;in;i+=2)if(is_prime(i)count+;print_prime(i,count);coutendl;return0;,boolis_prime(intn)inti,j,k=sqrt(n);for(i=2,j=k;i=j;i+)if(n%i=0)returnfalse;returntrue;voidprint_prime(intn,intcount)coutn,;if(count%6=0)coutendl;,函数的参数传递,C+提供了两种参数传递机制:值传递把实参的值赋值给形参。地址或引用传递把实参的地址赋值给形参。C+默认的参数传递方式是值传递。,值传递,在函数调用时,采用类似变量初始化的形式把实参的值传给形参。函数执行过程中,通过形参获得实参的值,函数体中对形参值的改变不会影响相应实参的值。,值参数传递的例子,/函数main调用函数power计算ab#includeusingnamespacestd;doublepower(doublex,intn);intmain()doublea=3.0,c;intb=4;c=power(a,b);couta,b,c0)product*=x;n-;elsewhile(n0)product/=x;n+;returnproduct;,执行main时,产生三个变量(分配内存空间)a、b和c:a:3.0b:4c:?调用power函数时,又产生三个个变量x、n和product,然后分别用a、b以及1.0对它们初始化:a:3.0b:4c:?x:3.0n:4product:1.0函数power中的循环结束后(函数返回前):a:3.0b:4c:?x:3.0n:0product:81.0函数power返回后:a:3.0b:4c:81.0,变量的局部性,在C+中,根据变量的定义位置,把变量分成:局部变量和全局变量。局部变量是指在复合语句中定义的变量,它们只能在定义它们的复合语句(包括内层的复合语句)中使用。全局变量是指在函数外部定义的变量,它们一般能被程序中的所有函数使用(静态的全局变量除外)。,局部变量和全局变量的例子,intx=0;/全局变量voidf()inty=0;/局部变量x+;/OKy+;/OKa+;/Error,intmain()inta=0;/局部变量f();a+;/OKx+;/OKy+;/Errorwhile(x10)intb=0;/局部变量a+;/OKb+;/OKx+;/OKb+;/Errorreturn0;,变量的生存期(存储分配),把程序运行时一个变量占有内存空间的时间段称为该变量的生存期。静态:从程序开始执行时就进行内存空间分配,直到程序结束才收回它们的空间。全局变量具有静态生存期。自动:内存空间在程序执行到定义它们的复合语句(包括函数体)时才分配,当定义它们的复合语句执行结束时,它们的空间将被收回。局部变量和函数的参数一般具有自动生存期。动态:内存空间在程序中显式地用new操作或malloc库函数分配、用delete操作或free库函数收回。动态变量具有动态生存期。具有静态生存期的变量,如果没有显式初始化,系统将把它们初始化成0。,存储类修饰符,在定义局部变量时,可以为它们加上存储类修饰符来显式地指出它们的生存期。auto:使局部变量具有自动生存期。局部变量的默认存储类为auto。static:使局部变量具有静态生存期。它只在函数第一次调用时进行初始化,以后调用中不再进行初始化,它的值为上一次函数调用结束时的值。register:使局部变量也具有自动生存期,由编译程序根据CPU寄存器的使用情况来决定是否存放在寄存器中。,voidf()autointx=0;/auto一般不写staticinty=1;registerintz=0;x+;y+;z+;coutxyz调用f时,输出:x=1,y=3,z=1,Static实例,程序实体在内存中的安排,程序运行时各种数据在内存中的分配:静态数据区用于全局变量、static存会储类的局部变量以及常量的内存分配。(该区变量未初始化时会默认为0)代码区用于存放程序的指令,对C+程序而言,代码区存放的是所有函数代码;栈区用于auto存储类的局部变量、函数的形式参数以及函数调用时有关信息(如:函数返回地址等)的内存分配;堆区用于动态变量的内存分配。,例7:编写一个能够产生随机数的函数usingnedintrandom()staticusignedintseed=1;seed=(25173*seed+13849)%65536;returnseed;,C+程序的多模块结构,逻辑上,一个C+程序由一些全局函数(区别于类定义中的成员函数)、全局常量、全局变量/对象以及类的定义构成,其中必须有且仅有一个名字为main的全局函数。函数内部可以包含形参、局部常量、局部变量/对象的定义以及语句。物理上,可以按某种规则对构成C+程序的各个逻辑单位(全局函数、全局常量、全局变量/对象、类等)的定义进行分组,分别把它们放在若干个源文件中,构成程序模块。程序模块是为了便于从物理上对程序进行组织、管理和理解,便于多人合作开发一个程序。程序模块是可单独编译的程序单位。,C+模块的构成,一个C+模块一般包含两个部分:接口(.h文件):给出在本模块中定义的、提供给其它模块使用的一些程序实体(如:函数、全局变量等)的声明;实现(.cpp文件):模块的实现给出了模块中的程序实体的定义。,在模块A中要用到模块B中定义的程序实体时,可以在A的.cpp文件中用文件包含命令(#include)把B的.h文件包含进来。文件包含命令是一种编译预处理命令,其格式为:#include或#include文件名“注意:(1)#include表示在系统指定的目录下寻找指定文件(2)#include“文件名“表示在源文件所在的目录下寻找指定文件include命令的含义是:在编译前,用命令中的文件名所指定的文件内容替换该命令。,/file1.hexternintx;/全局变量x的声明externdoubley;/全局变量y的声明intf();/全局函数f的声明/file1.cppintx=1;/全局变量x的定义doubley=2.0;/全局变量y的定义intf()/全局函数f的定义intm;/局部变量m的定义.m+=x;/语句.returnm;,/file2.hvoidg();/全局函数g的声明/file2.cpp#includefile1.h/把文件file1.h中的内容包含进来voidg()/全局函数g的定义doublez;/局部变量z的定义.z=y+10;/语句.,/main.cpp#includefile1.h/把文件file1.h中的内容包含进来#includefile2.h/把文件file2.h中的内容包含进来intmain()/全局函数main的定义doubler;/局部变量r的定义.r=x+y*f();/语句.g();/语句.,标识符的作用域,在程序常量、变量、函数、类等的命名中,要区分它们需要命不同的名字,这样程序才能区分它们。但这会带来不便。如两个程序员写函数的局部变量,可能会出现命名相同。为了对程序中的实体的名字进行管理,引进了标识符的作用域的概念。一个定义了的标识符的有效范围(能被访问的程序段)称为该标识符的作用域。在不同的作用域中,可以用相同的标识符来标识不同的程序实体。,C+标识符的作用域,C+把标识符的作用域分成若干类,其中包括:局部作用域全局作用域文件作用域函数作用域函数原型作用域类作用域名空间作用域,局部作用域,在函数定义或复合语句中、从标识符的定义点开始到函数定义或复合语句结束之间的程序段。C+中的局部常量名、局部变量名/对象名以及函数的形参名具有局部作用域。,例:voidf(inty)y;/OKx;/Errorintx;x;/OKdoublex;/Error,重名chary;/Error,重名,voidg(doubley)/OKdoublex;/OKf(1);/OK,voidf(intn)x+;/Errorintx=0;x+;n+;.voidg()x+;/Errorn+;/Errorintmain()intx=0;intn;cinn;f(n);.,如果在一个标识符的局部作用域中包含内层复合语句,并且在该内层复合语句中定义了一个同名的不同实体,则外层定义的标识符的作用域应该是从其潜在作用域中扣除内层同名标识符的作用域之后所得到的作用域。voidf()intx;/外层x的定义.x./外层的xwhile(.x.)/外层的x.x./外层的x,doublex;/内层x的定义.x./内层的x.x./外层的x,全局作用域,在函数级定义的标识符具有全局作用域。全局变量名/对象名、全局函数名和全局类名的作用域一般具有全局作用域,它们在整个程序中可用。如果在某个局部作用域中定义了与某个全局标识符同名的标识符,则该全局标识符的作用域应扣掉与之同名的局部标识符的作用域。在局部标识符的作用域中若要使用与其同名的全局标识符,则需要用全局域选择符(:)对全局标识符进行修饰(受限)。,doublex;/外层x的定义voidf()intx;/内层x的定义.x./内层的x:x./外层的x,作用域的综合实例,文件作用域,在全局标识符的定义中加上static修饰符,则该全局标识符就成了具有文件作用域的标识符,它们只能在定义它们的源文件(模块)中使用。C+中的关键词static有两个不同的含义。在局部变量的定义中,static修饰符用于指定局部变量采用静态存储分配;而在全局标识符的定义中,static修饰符用于把全局标识符的作用域改变为文件作用域。一般情况下,具有全局作用域的标识符主要用于标识被程序各个模块共享的程序实体,而具有文件作用域的标识符用于标识在一个模块内部共享的程序实体。,/file1.cppstaticinty;/文件作用域staticvoidf()/文件作用域./file2.cppexterninty;externvoidf();voidg().y./Errorf();/Error,函数作用域,语句标号是唯一具有函数作用域的标识符,它们在定义它们的函数体中的任何地方都可以访问。函数作用域与局部作用域的区别是:函数作用域包括整个函数,而局部作用域是从定义点开始到函数定义或复合语句结束。在函数体中,一个语句标号只能定义一次,即使是在内层的复合语句中,也不能再定义与外层相同的语句标号。,voidf().gotoL;/OK.L:.L:./Error.gotoL;/OK.voidg().gotoL;/Error.,名空间作用域,对于一个多文件构成的程序,有时会面临一个问题:在一个源文件中要用到两个分别在另外两个源文件中定义的不同全局程序实体(如:全局函数),而这两个全局程序实体的名字相同。例C+提供了名空间(namespace)设施来解决上述的名冲突问题。在一个名空间中定义的全局标识符,其作用域为该名空间。当在一个名空间外部需要使用该名空间中定义的全局标识符时,可用该名空间的名字来修饰或受限。,/file1.cppvoidf().,/file2.cppvoidf().,intmain()f();/file1中还是file2中的?f();/file1中还是file2中的?,/main.cpp,/模块1namespaceAintx=1;voidf().,/模块2namespaceBintx=0;voidf().,.A:x./A中的xA:f();/A中的f.B:x./B中的xB:f();/B中的f,usingnamespaceA;.x./A中的xf();/A中的f.B:x./B中的xB:f();/B中的f,usingA:f;.A:x./A中的xf();/A中的f.B:x./B中的xB:f();/B中的f,/模块3,1、,2、,3、,递归函数,函数的调用是可以嵌套的。voidh().voidg().h();.voidf().g();.,直接递归voidf().f().,间接递归externvoidg();voidf().g().voidg().f().,如果一个函数在其函数体中直接或间接地调用了自己,则该函数称为递归函数。,递归函数的作用,在程序设计中经常需要实现重复性的操作。循环为实现重复操作提供了一种途径。实现重复操作的另一个途径是采用递归函数。“分而治之”(DivideandConquer)设计方法:把一个问题分解成若干个子问题,而每个子问题的性质与原问题相同,只是在规模上比原问题要小。每个子问题的求解过程可以采用与原问题相同的方式来进行。递归函数为上述设计方法提供了一种自然、简洁的实现机制,例:求第n个fibonacci数(递归解法),intfib(intn)if(n=1)return0;elseif(n=2)return1;elsereturnfib(n-2)+fib(n-1);,递归函数的执行过程,/用递归函数求n!intf(intn)if(n=0)return1;elsereturnn*f(n-1);,递归条件和结束条件,在定义递归函数时,一定要对两种情况给出描述:递归条件。指出何时进行递归调用,它描述了问题求解的一般情况,包括:分解和综合过程。结束条件。指出何时不需递归调用,它描述了问题求解的特殊情况或基本情况,例:解汉诺塔问题,汉诺塔问题:有A,B,C三个柱子,柱子A上穿有n个大小不同的圆盘,大盘在下,小盘在上。现要把柱子A上的所有圆盘移到柱子B上,要求每次只能移动一个圆盘,且大盘不能放在小盘上,移动时可借助柱子C。编写一个C+函数给出移动步骤,如:n=3时,移动步骤为:1:AB,2:AC,1:BC,3:AB,1:CA,2:CB,1:AB。,ABC,当n=1时,只要把1个圆盘从A移至B就可以了coutBendl;把n-1个圆盘从柱子C移到柱子B。上面的子问题1和3与原问题相同,只是盘子的个数少了一个以及移动的位置不同;子问题2是移动一个盘子的简单问题。,#includeusingnamespacestd;voidhanoi(charx,chary,charz,intn)/把n个圆盘从x表示的柱子移至y所表示的柱子。if(n=1)cout1:xyendl;/把第1个盘子从x表示的柱子移至y所表示的柱子。elsehanoi(x,z,y,n-1);/把n-1个圆盘从x表示的柱子移至z所表示的柱子。coutn:xy函数首地址例如,对于下面的重载函数定义:voidprint(int);voidprint(double);voidprint(char);下面的函数调用:print(1);绑定到函数:voidprint(int);print(1.0);绑定到函数:voidprint(double);print(a);绑定到函数:voidprint(char);,提升匹配,先对实参进行下面的类型提升,然后进行精确匹配:按整型提升规则提升实参类型把float类型实参提升到double把double类型实参提升到longdouble例如,对于下述的重载函数:voidprint(int);voidprint(double);根据提升匹配,下面的函数调用:print(a);绑定到函数:voidprint(int);print(1.0f);绑定到函数:voidprint(double);,标准转换匹配,任何算术类型可以互相转换枚举类型可以转换成任何算术类型零可以转换成任何算术类型或指针类型任何类型的指针可以转换成void*派生类指针可以转换成基类指针以上转换规则优先级相同。,例如,对于下述的重载函数:voidprint(char);voidprint(char*);根据标准转换匹配,下面的函数调用:print(1);绑定到函数:voidprint(char);,绑定失败,如果不存在匹配或存在多个匹配,则绑定失败例如,对于下述的重载函数:voidprint(char);voidprint(double);根据标准转换匹配,下面的函数调用将会绑定失败:print(1);因为根据标准转换,1(属于int型)既可以转成char,又可以转成double解决办法是:对实参进行显式类型转换,如,print(char)1)或print(double)1)增加额外的重载,如,增加一个重载函数定义:voidprint(int);,带缺省值的形式参数,在C+中允许在声明函数时,为函数的某些参数指定默认值。如果调用这些函数时没有提供相应的实参,则相应的形参采用指定的默认值。例如,对于下面的函数声明:voidprint(intvalue,intbase=10);下面的调用:print(28);/28传给value;10传给baseprint(32,2);/28传给value;2传给base,在指定函数参数的默认值时,应注意下面几点:有默认值的形参应处于形参表的右部。例如:voidf(inta,intb=1,intc=0);/OKvoidf(inta,intb=1,intc);/Error对参数默认值的指定只在函数声明(包括定义性声明)处有意义。在不同的源文件中,对同一个函数的声明可以对它的同一个参数指定不同的默认值;在同一个源文件中,对同一个函数的声明只能对它的每一个参数指定一次默认值。,无名参数,在C+中允许在函数声明和定义时将参数名省略,这些参数称为无名参数。函数声明时允许使用无名参数voidprint(int,int);函数定义时使用无名参数voidpoint(inta,int)coutab?a:b10+max(x,y)+z将被替换成:10+xy?x:y+z有时会出现重复计算。例如:#definemax(a,b)(a)(b)?(a):(b)max(x+1,y*2)将被替换成:(x+1)(y*2)?(x+1):(y*2)不进行参数类型检查和转换。不利于一些工具对程序的处理。,内联函数,内联函数是指在函数定义时,在函数返回类型之前加上一个关键词inline,例如:inlineintmax(inta,intb)returnab?a:b;内联函数的作用是建议编译程序把该函数的函数体展开到调用点,以提高函数调用的效率。内联函数形式上属于函数,它遵循函数的一些规定,如:参数类型检查与转换。使用内联函数时应注意以下几点:编译程序对内联函数的限制。内联函数名具有文件作用域。,编译预处理命令,C+程序中可以写一些供编译程序使用的命令:编译预处理命令。编译预处理命令不是C+程序所要完成的功能,而是用于对编译过程给出指导,其功能由编译预处理系统来完成。编译预处理命令主要有:文件包含命令(#include)宏定义(#define)命令条件编译命令,条件编译,编译程序根据不同的情况来选择需编译的程序代码。例如,编译程序将根据宏名ABC是否被定义,来选择需要编译的代码(或):/必须编译的代码#ifdefABC/如果宏名ABC有定义,编译之#else/如果宏名ABC没有定义,编译之#endif/必须编译的代码用于条件编译的宏名ABC在哪里定义?,在程序中定义宏/必须编译的代码#defineABC#ifdefABC/如果宏名ABC有定义,编译之#else/如果宏名ABC没有定义,编译之#endif/必须编译的代码缺点:要修改程序!,在编译环境中定义宏命令行cl.-DABC.VisualC+6.0的集成开发环境中,选择“Project|Settings”菜单,在“ProjectSettings”对话框的“C/C+”选项卡中,选择“Category”中的“Preprocessor”,然后在“Preprocessordefinitions”中添加要定义的宏名:ABC。,条件编译命令的常用格式,#ifdef/#ifndef#else#endif上述条件编译命令的含义是:如果有定义(#ifdef)或无定义(#ifndef),则编译,否则编译,其中,#else分支可以省略。可以在程序中用#define定义,也可以在编译器的选项中给出。,条件编译命令的另一种格式,#if/#ifdef/#ifndef#elif.#elif#else#endif上述条件编译命令的含义是:如果的值为非零(#if)或有定义(#ifdef)或无定义(#ifndef),则编译,否则,如果为非零值,则编译,.,否则如果有#else,则编译,否则什么都不编译。中只能包含字面常量或用#define定义的常量。,条件编译的作用,条件编译的作用:基于多环境的程序编制程序调试.,基于多环境的程序编制,#ifdefUNIX./适合于UNIX环境的代码#elifWINDOWS./适合于WINDOWS环境的代码#else./适合于其它环境的代码#e

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论