《过程抽象函数》PPT课件.ppt

上传人:牧羊曲112 文档编号:5610756 上传时间:2023-08-02 格式:PPT 页数:67 大小:321.99KB
返回 下载 相关 举报
《过程抽象函数》PPT课件.ppt_第1页
第1页 / 共67页
《过程抽象函数》PPT课件.ppt_第2页
第2页 / 共67页
《过程抽象函数》PPT课件.ppt_第3页
第3页 / 共67页
《过程抽象函数》PPT课件.ppt_第4页
第4页 / 共67页
《过程抽象函数》PPT课件.ppt_第5页
第5页 / 共67页
点击查看更多>>
资源描述

《《过程抽象函数》PPT课件.ppt》由会员分享,可在线阅读,更多相关《《过程抽象函数》PPT课件.ppt(67页珍藏版)》请在三一办公上搜索。

1、第四章 过程抽象函数,本章内容,4.1 子程序4.2 C+的函数4.3 变量的局部性和变量的生存期4.4 函数的嵌套调用4.5 递归函数4.6 宏定义4.7 内联函数4.8 带缺省值的形式参数4.9 函数名重载,基于过程抽象的程序设计,人们在设计一个复杂的程序时,经常会用到功能分解和复合两种手段:功能分解:在进行程序设计时,首先把程序的功能分解成若干子功能,每个子功能又可以分解成若干子功能,等等,从而形成了一种自顶向下(top-down)、逐步精化(step-wise)的设计过程。功能复合:把已有的(子)功能逐步组合成更大的(子)功能,从而形成一种自底向上(bottom-up)的设计过程。过程

2、抽象:一个功能的使用者只需要知道相应功能是什么(what to do),而不必知道它是如何做(how to do)的。,4.1 子程序,子程序是取了名的一段程序代码,在程序中通过名字来使用(调用)它们。子程序的作用:减少重复代码,节省劳动力实现过程抽象(功能抽象)封装和信息隐藏的作用 语言功能的扩充,4.2 C+函数,函数:完成相对独立功能的子程序.标准库函数(由系统定义,用户可直接调用)自定义函数(用户需要时,自己定义),C语言函数,4.2.1 C+标准库函数,为了方便程序设计,C+语言的每个实现往往会提供一个标准库,其中定义了一些语言本身没有提供的功能:常用的数学函数字符串处理函数以及输入

3、/输出,等等在C+标准库中,根据功能对定义的程序实体进行了分类,把每一类程序实体的声明分别放在一个头文件中。在C+中,把从C语言保留下来的库函数,重新定义在名空间std中;对相应的头文件进了重新命名:*.h-c*,一些标准数学函数(cmath或math.h),int abs(int n);/int型的绝对值long labs(long n);/long int型的绝对值double fabs(double x);/double型的绝对值double sin(double x);/正弦函数double cos(double x);/余弦函数double tan(double x);/正切函数do

4、uble asin(double x);/反正弦函数double acos(double x);/反余弦函数double atan(double x);/反正切函数double ceil(double x);/不小于x的最小整数(返回值为以/double表示的整型数)double floor(double x);/不大于x的最大整数(返回值为以/double表示的整型数)double log(double x);/自然对数double log10(double x);/以10为底的对数double sqrt(double x);/平方根double pow(double x,double y)

5、;/x的y次幂,4.2.1 C+标准库函数,库函数使用举例#include/相应的头文件#include/相应的头文件using namespace std;/重新定义在名空间std中;void main()double pi=3.1415926535;double x,y;x=pi/2;y=sin(x);cout sin(x)=y endl;y=cos(x);cout cos(x)=y endl;,4.2.2 自定义 C+函数,1.函数的定义:();描述了函数返回值的类型,可以为任意的C+数据类型。当返回值类型为void时,它表示函数没有返回值。用于标识函数的名字,用标识符表示。描述函数的形

6、式参数,由零个、一个或多个形参说明(用逗号隔开)构成,形参说明的格式为:,4.2.2 自定义 C+函数,为一个,用于实现相应函数的功能。函数体内可以包含return语句,格式为:return;return;当函数体执行到return语句时,函数立即返回到调用者。如果有返回值,则把返回值带回给调用者。如果return中的的类型与函数 不一致,则进行隐式类型转换,基本原则为:把转成。注意:在函数体中不能用goto语句转出函数体。,4.2.2 自定义 C+函数,double max(double x,double y)if(x y)return x;elsereturn y;,函数定义举例:求两个浮

7、点数中较大数,4.2.2 自定义 C+函数,每个C+程序都要定义一个名字为main的函数,C+程序的执行是从main开始的。对于函数main,其返回值类型为int,例如:int main().return-1;.return 0;一般情况下,返回0表示程序正常结束;返回负数(如1)表示程序非正常结束。,2.函数main,3.函数的调用,对于定义的一个函数,必须要调用它,它的函数体才会执行。函数调用的格式如下:()实参的个数和类型应与相应函数的形参相同。类型如果不同,编译器会试图进行隐式转换,转换规则是把实参类型转换成形参类型。注意:不能用goto语句从函数外转入函数体,4.2.2 自定义 C+

8、函数,4.2.2 自定义 C+函数,3.函数调用举例-1#include using namespace std;float max(float x,float y)/必须分别定义 float z;z=xy?x:y;return(z);void main()float a,b;int c;cinab;c=max(a,b);coutMax is cendl;,函数声明,程序中调用的所有函数都要有定义。如果函数定义在其它文件(如:C+的标准库)中或定义在本源文件中使用点之后,则在调用前需要对被调用的函数进行声明。函数声明的格式如下:();/函数原型或extern();在函数声明中,中可以只列出形参

9、的类型而不写形参名,4.2.2 自定义 C+函数,4.2.2 自定义 C+函数,3.函数调用举例-2#include#include using namespace std;float max(float,float);/函数声明void main()float a,b;int c;cinab;c=max(a,b);couty?x:y;return(z);,/file2.cppint g(int i)/定义 extern int x,y;/声明 int z;/定义 z=x+y;return z+i;,/file1.cppint x=0;/定义int main()/定义 extern void

10、f();/声明 extern int g(int);/声明 extern int y;/声明 y=x+2;f();/调用 y=g(x);/调用 return 0;int y=0;/定义void f()/定义x=y+1;,函数声明的作用是什么?,4.函数调用的执行过程,计算实参的值(对于多个实参,C+没有规定计算次序);把实参分别传递给被调用函数的形参;执行函数体;函数体中执行return语句返回函数调用点,调用点获得返回值(如果有返回值)并执行调用之后的操作。,4.2.2 自定义 C+函数,【example4_5】用函数实现求小于n的所有素数。,#include#include using n

11、amespace std;bool is_prime(int n);/函数声明void print_prime(int n,int count);/函数声明int main()int i,n,count=1;cout n;/从键盘输入一个正整数if(n 2)return-1;cout 2,;/输出第一个素数for(i=3;in;i+=2)if(is_prime(i)count+;print_prime(i,count);cout endl;return 0;,bool is_prime(int n)int i,j,k=sqrt(n);for(i=2,j=k;i=j;i+)if(n%i=0)re

12、turn 0;return 1;void print_prime(int n,int count)cout n,;if(count%6=0)cout endl;,4.2.3 函数的参数传递,C+提供了两种参数传递机制:值传递把实参的值赋值给形参。地址或引用传递把实参的地址赋值给形参。C+默认的参数传递方式是值传递。,值传递,在函数调用时,采用类似变量初始化的形式把实参的值传给形参。函数执行过程中,通过形参获得实参的值。函数体中对形参值的改变不会影响相应实参的值。,值参数传递的例子,#include#include using namespace std;void swap(int a,int

13、b)int t;t=a;a=b;b=t;couta=a,b=bendl;void main()int x=3,y=4;coutx=x,y=yendl;swap(x,y);coutx=x,y=yendl;,执行main时,产生2个变量(分配内存空间)x和y:x:3 y:4 调用swap函数时,又产生3个变量a、b和t:a:3 b:4 t:?函数swap中的交换结束后(函数返回前):a:4 b:3 t:3函数swap返回后:x:3 y:4,4.3 变量的作用域及存储分配,1.局部变量和全局变量根据变量的定义位置,把变量分成:局部变量和全局变量。局部变量是指在复合语句中定义的变量,它们只能在定义它们

14、的复合语句(包括内层的复合语句)中使用。全局变量是指在函数外部定义的变量,它们一般能被程序中的所有函数使用(静态的全局变量除外)。,4.3 变量的作用域及存储分配,int x=0;/全局变量void f()int y=0;/局部变量x+;/OKy+;/OKa+;/Error,int main()int a=0;/局部变量f();a+;/OKx+;/OKy+;/Error while(x10)int b=0;/局部变量 a+;/OKb+;/OKx+;/OKb+;/Errorreturn 0;,全局变量和局部变量的区别,【举例1:全局变量应用】,#include#include using nam

15、espace std;int a,b;/a,b为全局变量void f1()int t1,t2;t1=a*2;t2=b*3;b=100;coutt1=t1,t2=t2endl;void main()a=2;b=4;/此a,b是全局变量,赋值 f1();/调用函数f1()couta=a,b=bendl;,【举例2:全局变量和局部变量同名】,#include#include using namespace std;int a=2,b=4;/a,b为全局变量void f1()int t1,t2;t1=a*2;t2=b*3;b=100;coutt1=t1,t2=t2endl;void main()int

16、 b=4;/同名局部变量 f1();couta=a,b=bendl;,全局变量与局部变量同名时,则在局部变量的作用范围内,外部变量不起作用。,2.变量的生存期(存储分配),(1)把程序运行时一个变量占有内存空间的时间段称为该变量的生存期。静态:从程序开始执行时就进行内存空间分配,直到程序结束才收回它们的空间。全局变量具有静态生存期。自动:内存空间在程序执行到定义它们的复合语句(包括函数体)时才分配,当定义它们的复合语句执行结束时,它们的空间将被收回。局部变量和函数的参数一般具有自动生存期。动态:内存空间在程序中显式地用new操作或malloc库函数分配、用delete操作或free库函数收回。

17、动态变量具有动态生存期。具有静态生存期的变量,如果没有显式初始化,系统将把它们初始化成0。,(2)存储类修饰符,auto:使局部变量具有自动生存期。局部变量的默认存储类为auto。static:使局部变量具有静态生存期。它只在函数第一次调用时进行初始化,以后调用中不再进行初始化,它的值为上一次函数调用结束时的值。register:使局部变量也具有自动生存期,由编译程序根据CPU寄存器的使用情况来决定是否存放在寄存器中。,2.变量的生存期(存储分配),存储特性数据类型 变量名;,完整的变量定义:,2.变量的生存期(存储分配),auto型:每次进入程序是自动分配内存,不长期占用内存 例如:形式参数

18、,自动型局部变量static 型:局部静态变量:静态变量定义在函数中 全局静态变量:静态变量 定义在函数外长期占用内存,只进行一次初始化,【举例3:变量生存期】,#include#include using namespace std;int f(int a)int b=0;static int c=3;b+;c+;coutatbtct;return(a+b+c);void main()int a=2,k;for(k=0;k3;k+)coutf(a)endl;,3.程序实体在内存中的安排,静态数据区用于全局变量、static存储类的局部变量以及常量的内存分配。代码区用于存放程序的指令,对C+程

19、序而言,代码区存放的是所有函数代码;栈区用于auto存储类的局部变量、函数的形式参数以及函数调用时有关信息(如:函数返回地址等)的内存分配;堆区用于动态变量的内存分配。,4.标识符的作用域,为了对程序中的实体的名字进行管理,引进了标识符的作用域的概念。一个定义了的标识符的有效范围(能被访问的程序段)称为该标识符的作用域。在不同的作用域中,可以用相同的标识符来标识不同的程序实体。,4.C+标识符的作用域,C+把标识符的作用域分成若干类,其中包括:局部作用域全局作用域文件作用域函数作用域函数原型作用域类作用域名空间作用域,(1)局部作用域,在函数定义或复合语句中、从标识符的定义点开始到函数定义或复

20、合语句结束之间的程序段。C+中的局部常量名、局部变量名/对象名以及函数的形参名具有局部作用域。,4.C+标识符的作用域,void f(int n)x+;/Error int x=0;x+;n+;.void g()x+;/Error n+;/Errorint main()int x=0;int n;cin n;f(n);.,如果在一个标识符的局部作用域中包含内层复合语句,并且在该内层复合语句中定义了一个同名的不同实体,则外层定义的标识符的作用域应该是从其潜在作用域中扣除内层同名标识符的作用域之后所得到的作用域。void f()int x;/外层x的定义.x./外层的xwhile(.x.)/外层的

21、x.x./外层的x,double x;/内层x的定义.x./内层的x.x./外层的x,(2)全局作用域,在函数级定义的标识符具有全局作用域。全局变量名/对象名、全局函数名和全局类名的作用域一般具有全局作用域,它们在整个程序中可用。在局部标识符的作用域中若要使用与其同名的全局标识符,则需要用全局域选择符(:)对全局标识符进行修饰(受限)。,4.C+标识符的作用域,double x;/外层x的定义void f()int x;/内层x的定义.x./内层的x.:x./外层的x,4.C+标识符的作用域,(2)全局作用域,(3)文件作用域,在全局标识符的定义中加上static修饰符,则该全局标识符就成了具

22、有文件作用域的标识符,它们只能在定义它们的源文件(模块)中使用。,4.C+标识符的作用域,/file1.cppstatic int y;/文件作用域static void f()/文件作用域./file2.cppextern int y;extern void f();void g().y./Error f();/Error,(4)名空间作用域,对于一个多文件构成的程序,有时会面临一个问题:在一个源文件中要用到两个分别在另外两个源文件中定义的不同全局程序实体(如:全局函数),而这两个全局程序实体的名字相同。C+提供了名空间(namespace)设施来解决上述的名冲突问题。在一个名空间中定义的全

23、局标识符,其作用域为该名空间。当在一个名空间外部需要使用该名空间中定义的全局标识符时,可用该名空间的名字来修饰或受限。,4.C+标识符的作用域,3、,/模块2namespace Bint x=0;void f().,.A:x./A中的x A:f();/A中的f.B:x./B中的xB:f();/B中的f,using namespace A;.x./A中的x f();/A中的f.B:x./B中的xB:f();/B中的f,using A:f;.A:x./A中的x f();/A中的f.B:x./B中的xB:f();/B中的f,/模块3,1、,2、,4.4 函数的嵌套调用,函数的调用是可以嵌套的。voi

24、d h().void g().h();.void f().g();.,直接递归void f().f().,间接递归extern void g();void f().g().void g().f().,1.定义:如果一个函数在其函数体中直接或间接地调用了自己,则该函数称为递归函数。,4.5 递归函数,2.递归调用的条件,递归模型,递归终止条件,递归体,确定递归的方式将一个问题转化成一个新问题,且解题方法相似,每一步转化都越接近终止条件。,4.5 递归函数,【example4_9】求第n个fibonacci 数(递归解法),int fib(int n)if(n=1|n=2)return 1;els

25、e return fib(n-2)+fib(n-1);,3.递归函数的执行过程,/用递归函数求n!int f(int n)if(n=0)return 1;else return n*f(n-1);,【example4_13】解汉诺塔问题,汉诺塔问题:有A,B,C三个柱子,柱子A上穿有n个大小不同的圆盘,大盘在下,小盘在上。现要把柱子A上的所有圆盘移到柱子B上,要求每次只能移动一个圆盘,且大盘不能放在小盘上,移动时可借助柱子C。编写一个C+函数给出移动步骤。,A B C,分析:,当n=1时,只要把1个圆盘从A移至B就可以了cout B endl;把n-1个圆盘从柱子C移到柱子B。上面的子问题1和

26、3与原问题相同,只是盘子的个数少了一个以及移动的位置不同;子问题2是移动一个盘子的简单问题。,#include using namespace std;void hanoi(char x,char y,char z,int n)/n个圆盘,xyif(n=1)cout yelsehanoi(x,z,y,n-1);/把n-1个,x-zcout yhanoi(z,y,x,n-1);/把n-1个圆盘,z-yvoid main()int n;cout n;hanoi(A,B,C,n);,4.递归与循环的选择,对于一些递归定义的问题,用递归函数来解决会显得比较自然和简洁,而用循环来解决这样的问题,有时会很

27、复杂,不易设计和理解。在实现数据的操作上,它们有一点不同:循环是在同一组变量上进行重复操作(循环常常又称为迭代)递归则是在不同的变量组(属于递归函数的不同实例)上进行重复操作。递归的缺陷:由于递归表达的重复操作是通过函数调用来实现的,而函数调用是需要开销的;栈空间的大小也会限制递归的深度。递归算法有时会出现重复计算。,解决小函数的低效问题,由于函数调用是需要开销的,特别是对一些小函数的频繁调用将使程序的效率有很大的降低。C+提供了两种解决上述问题的办法:宏定义内联函数,4.6 宏定义,在C+中,利用一种编译预处理命令:宏定义,用它可以实现类似函数的功能:#define凵()凵例如:#defin

28、e凵max(a,b)凵(a)(b)?(a):(b)在编译之前,将对宏的使用进行文字替换!例如:编译前将把cout(y)?(x):(y);,宏定义的不足之处,需要加上很多的括号。例如:#define max(a,b)ab?a:b 10+max(x,y)+z 将被替换成:10+xy?x:y+z 有时会出现重复计算。例如:#define凵max(a,b)凵(a)(b)?(a):(b)max(x+1,y*2)将被替换成:(x+1)(y*2)?(x+1):(y*2)不进行参数类型检查和转换。不利于一些工具对程序的处理。,#include using namespace std;#define ADD(x

29、)x+xvoid main()int m=1,n=2,k=3;int sum=ADD(m+n)*k;coutsum=sumendl;上面程序的运行结果是A)sum=9 B)sum=10 C)sum=12 D)sum=18,4.7 内联函数,内联函数是指在定义函数定义时,在函数返回类型之前加上一个关键词inline,例如:inline int max(int a,int b)return ab?a:b;内联函数的作用是建议编译程序把该函数的函数体展开到调用点,以提高函数调用的效率。,4.7 内联函数,使用内联函数时应注意以下几点:若inline不在函数名第一次出现时指定,则编译器把它作为普通函数

30、处理。一般内联函数只适合于1到5行的小程序。递归函数不能说明为内联函数。为了防止同一个内联函数的各个定义之间的不一致,往往把内联函数的定义放在某个头文件中,在需要使用该内联函数的源文件中用文件包含命令#include把该头文件包含进来。由于内联函数名具有文件作用域,因此,不会出现重复定义问题。,举例:从键盘上输入一串字符,以回车结束,统计其中数字字符个数。,#include#include inline int isnumber(char);void main()char c;int n;n=0;while(c=getchar()!=n)if(isnumber(c)n+;cout=0,4.8

31、带缺省值的形式参数,在C+中允许在声明函数时,为函数的某些参数指定默认值。如果调用这些函数时没有提供相应的实参,则相应的形参采用指定的默认值。例如,对于下面的函数声明:void print(int value,int base=10);下面的调用:print(28);/28传给value;10传给baseprint(32,2);/28传给value;2传给base,4.8 带缺省值的形式参数,在指定函数参数的默认值时,应注意下面几点:有默认值的形参应处于形参表的右部。例如:void f(int a,int b=1,int c=0);/OKvoid f(int a,int b=1,int c);

32、/Error 对参数默认值的指定只在函数声明(包括定义性声明)处有意义。在不同的源文件中,对同一个函数的声明可以对它的同一个参数指定不同的默认值;在同一个源文件中,对同一个函数的声明只能对它的每一个参数指定一次默认值。,4.9 函数名重载,对于一些功能相同、参数类型或个数不同的函数,有时给它们取相同的名字会带来使用上的方便。例如,把下面的函数:void print_int(int i).void print_double(double d).void print_char(char c).void print_A(A a)./A为自定义类型定义为:void print(int i).void

33、print(double d).void print(char c).void print(A a).上述的函数定义形式称为函数名重载。,1.对重载函数调用的绑定,确定一个对重载函数的调用对应着哪一个重载函数定义的过程称为绑定(binding,又称定联、联编、捆绑)。例如:print(1.0)将调用void print(double d).对重载函数调用的绑定在编译时刻由编译程序根据实参与形参的匹配情况来决定。从形参个数与实参个数相同的重载函数中按下面的规则选择一个:精确匹配提升匹配标准转换匹配自定义转换匹配匹配失败,(1)精确匹配,类型相同对实参进行“微小”的类型转换:数组变量名-数组首地址

34、函数名-函数首地址等等例如,对于下面的重载函数定义:void print(int);void print(double);void print(char);下面的函数调用:print(1);绑定到函数:void print(int);print(1.0);绑定到函数:void print(double);print(a);绑定到函数:void print(char);,(2)提升匹配,先对实参进行下面的类型提升,然后进行精确匹配:按整型提升规则提升实参类型把float类型实参提升到double把double类型实参提升到long double例如,对于下述的重载函数:void print(in

35、t);void print(double);根据提升匹配,下面的函数调用:print(a);绑定到函数:void print(int);print(1.0f);绑定到函数:void print(double);,(3)标准转换匹配,任何算术类型可以互相转换枚举类型可以转换成任何算术类型零可以转换成任何算术类型或指针类型任何类型的指针可以转换成void*派生类指针可以转换成基类指针每个标准转换都是平等的。,例如,对于下述的重载函数:void print(char);void print(char*);根据标准转换匹配,下面的函数调用:print(1);绑定到函数:void print(char);,2.绑定失败,如果不存在匹配或存在多个匹配,则绑定失败例如,对于下述的重载函数:void print(char);void print(double);根据标准转换匹配,下面的函数调用将会绑定失败:print(1);因为根据标准转换,1(属于int型)既可以转成char,又可以转成double 解决办法是:对实参进行显式类型转换,如,print(char)1)或 print(double)1)增加额外的重载,如,增加一个重载函数定义:void print(int);,

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 生活休闲 > 在线阅读


备案号:宁ICP备20000045号-2

经营许可证:宁B2-20210002

宁公网安备 64010402000987号