版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第四章过程抽象--函数本章内容子程序的概念
C++的函数变量的局部性标识符的作用域递归函数函数名重载带缺省值的形式参数内联函数条件编译标准库函数子程序子程序是取了名的一段程序代码,在程序中通过名字来使用它们。数据传递全局变量(不好):所有子程序都能访问到的变量。参数:形式参数(形参)和实在参数(实参)。传递方式有:值传递:把实参的值复制一份给形参。地址或引用传递:把实参的地址传给形参。子程序的作用:减少重复代码,节省劳动力;实现过程抽象,即,一个子程序代表一个功能,使用者只需知道所使用的子程序的功能,而不必关心它们是如何实现的。为解决复杂程序设计问题提供了一种手段。函数函数是C++提供的用于实现子程序的语言成分。函数的定义:
<返回值类型><函数名>(<形式参数表>)<函数体><返回值类型>描述了函数返回值的类型,可以为任意的C++数据类型。当返回值类型为void时,它表示函数没有返回值。<函数名>用于标识函数的名字,用标识符表示。<形式参数表>描述函数的形式参数,由零个、一个或多个形参说明(用逗号隔开)构成,形参说明的格式为:
<类型><形参名><函数体>
为一个<复合语句>,用于实现相应函数的功能。函数体内可以包含return语句,格式为:return<表达式>;return;当函数体执行到return语句时,函数立即返回到调用者。在函数体中不能用goto语句转出函数体。函数的例子int
factorial(intn)//求n的阶乘{ int
i,f=1; for(i=2;i<=n;i++)f*=i; returnf;}函数的调用
对于定义的一个函数,必须要调用它,它的函数体才会执行。函数调用的格式如下:<函数名>(<实在参数表>)<实在参数表>由零个、一个或多个表达式构成(逗号分割)实参的个数和类型应与相应函数的形参相同。类型如果不同,编译器会试图进行隐式转换。不能用goto语句从函数外转入函数体函数调用的例子......intmain(){ intx;
cout<<"请输入一个正整数:";
cin>>x;
cout<<"Factorialof"<<x<<"is" <<factorial(x)//调用阶乘函数
<<endl; return0;}函数调用的执行过程计算实参的值(对于多个实参,C++没有规定计算次序);把实参分别传递给被调用函数的形参;执行函数体;函数体中执行return语句返回函数调用点,调用点获得返回值(如果有返回值)并执行调用之后的操作。函数声明程序中调用的所有函数都要有定义。如果函数定义在其它文件(如:C++的标准库)中或定义在本源文件中使用点之后,则在调用前需要对被调用的函数进行声明。函数声明的格式如下:<返回值类型><函数名>(<形式参数表>);//函数原型extern<返回值类型><函数名>(<形式参数表>);在函数声明中,<形式参数表>中可以只列出形参的类型而不写形参名。基于功能分解和复合的程序设计人们在设计一个复杂的程序时,经常会用到功能分解和复合两种手段:功能分解:在进行程序设计时,首先把程序的功能分解成若干子功能,每个子功能又可以分解成若干子功能,等等,从而形成了一种自顶向下(top-down)、逐步精化(step-wise)的设计过程。功能复合:把已有的(子)功能逐步组合成更大的(子)功能,从而形成一种自底向上(bottom-up)的设计过程。子程序为基于功能分解和复合的程序设计提供了基础:一个子程序代表了一种功能。例:用函数实现求小于n的所有素数。#include<iostream>#include<cmath>usingnamespacestd;bool
is_prime(intn);//函数声明voidprint_prime(intn,intcount);//函数声明int
main(){ int
i,n,count=1;
cout<<"请输入一个正整数:"
cin>>n;//从键盘输入一个正整数
if(n<2)return-1;
cout<<2<<",";//输出第一个素数
for(i=3;i<n;i+=2) { if(is_prime(i)) { count++;
print_prime(i,count); }
}
cout<<endl; return0;}bool
is_prime(intn){ int
i,j; for(i=2,j=sqrt(n);i<=j;i++) if(n%i==0)returnfalse; returntrue;}voidprint_prime(intn,intcount){ cout<<n<<','; if(count%6==0)cout<<endl;}函数的参数传递C++提供了两种参数传递机制:值传递地址和引用传递C++默认的参数传递方式是值传递。值传递在函数调用时,采用类似赋值操作的形式把实参的值传给形参。函数执行过程中,通过形参获得实参的值,函数体中对形参值的改变不会影响相应实参的值。值参数传递的例子#include<iostream>usingnamespacestd;doublepower(doublex,intn);intmain(){ doublea=3.0,c;
intb=4; c=power(a,b);
cout<<a<<","<<b<<","<<c<<endl; return0;}doublepower(doublex,intn){ if(x==0)return0; doubleproduct=1.0; if(n>=0) while(n>0) { product*=x; n--; } else while(n<0) { 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++;//OK y++;//OK a++;//Error}intmain(){ inta=0;//局部变量
f(); a++;//OK x++;//OK y++;//Error while(x<10) {intb=0;//局部变量
a++;//OK b++;//OK x++;//OK } b++;//Error return0;}变量的生存期(存储分配)把程序运行时一个变量占有内存空间的时间段称为该变量的生存期。C++把变量的生存期分为:静态:内存空间从程序开始执行时就进行分配,直到程序结束才收回它们的空间。全局变量具有静态生存期。自动:内存空间在程序执行到定义它们的复合语句(包括函数体)时才分配,当定义它们的复合语句执行结束时,它们的空间将被收回。局部变量和函数的参数一般具有自动生存期。动态:内存空间在程序中显式地用new操作或malloc库函数分配、用delete操作或free库函数收回。动态变量具有动态生存期。
存储类修饰符在定义局部变量时,可以为它们加上存储类修饰符来指出它们的生存期。auto:使局部变量具有自动生存期。局部变量的默认存储类为auto。static:使局部变量具有静态生存期,它只在函数第一次调用时进行初始化,以后调用中不再进行初始化,它的值为上一次函数调用结束时的值。register:使局部变量也具有自动生存期,由编译程序根据CPU寄存器的使用情况来决定是否存放在寄存器中。voidf(){intx=0;staticinty=1;registerintz=0;x++;y++;z++;
cout<<x<<y<<z<<endl;}第一次调用f时,输出:121第二次调用f时,输出:131程序实体在内存中的安排静态数据区用于全局变量、static存储类的局部变量以及常量的内存分配。代码区用于存放程序的指令,对C++程序而言,代码区存放的是所有函数代码;栈区用于auto存储类的局部变量、函数的形式参数以及函数调用时有关信息(如:函数返回地址等)的内存分配;堆区用于动态变量的内存分配。静态数据区代码区栈区堆区C++程序的多模块结构逻辑上,一个C++程序由一些全局函数(区别于类定义中的成员函数)、全局常量、全局变量/对象以及类的定义构成,其中必须有且仅有一个名字为main的全局函数。函数内部可以包含形参、局部常量、局部变量/对象的定义以及语句。物理上,可以按某种规则对构成C++程序的各个逻辑单位(全局函数、全局常量、全局变量/对象、类等)的定义进行分组,分别把它们放在若干个源文件中,构成程序模块。程序模块是为了便于从物理上对程序进行组织、管理和理解,便于多人合作开发一个程序。程序模块是可单独编译的程序单位。C++模块的构成一个C++模块一般包含两个部分:接口(.h文件):给出在本模块中定义的、提供给其它模块使用的一些程序实体(如:函数、全局变量等)的声明;实现(.cpp文件):模块的实现给出了模块中的程序实体的定义。下面是一个由三个模块(file1、file2和main)构成的C++程序。//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#include"file1.h"//把文件file1.h中的内容包含进来voidg()//全局函数g的定义{ doublez;//局部变量z的定义
...... z=y+10;//语句
......}//main.cpp#include"file1.h"//把文件file1.h中的内容包含进来#include"file2.h"//把文件file2.h中的内容包含进来intmain()//全局函数main的定义{ doubler;//局部变量r的定义
...... r=x+y*f();//语句
...... g();//语句
......}标识符的作用域为了对程序中的实体的名字进行管理,引进了标识符的作用域的概念。一个定义了的标识符的有效范围(能被访问的程序段)称为该标识符的作用域。C++把标识符的作用域分成若干类,其中包括:局部作用域全局作用域文件作用域函数作用域函数原型作用域类作用域名空间作用域局部作用域在函数定义或复合语句中、从标识符的定义点开始到函数定义或复合语句结束之间的程序段。C++中的局部常量名、局部变量名/对象名以及函数的形参名具有局部作用域。如果在一个标识符的局部作用域中包含内层复合语句,并且在该内层复合语句中定义了一个同名的不同实体,则外层定义的标识符的作用域应该是从其潜在作用域中扣除内层同名标识符的作用域之后所得到的作用域。voidf(){ ...x...//Error
intx;//外层x的定义
...x...//外层的x while(...x...)//外层的x { ...x...//外层的x doublex;//内层x的定义
...x...//内层的x } ...x...//外层的x}全局作用域在函数级定义的标识符具有全局作用域。全局变量名/对象名、全局函数名和全局类名的作用域一般具有全局作用域,它们在整个程序中可用。如果在某个局部作用域中定义了与某个全局标识符同名的标识符,则该全局标识符的作用域应扣掉与之同名的局部标识符的作用域。在局部标识符的作用域中若要使用与其同名的全局标识符,则需要用全局域选择符(::)对全局标识符进行修饰(受限)。把全局标识符的声明放在某个.h文件中,在需要使用这些全局标识符的源文件中用#include编译预处理命令把声明文件包含进来。
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:... ......
gotoL;//OK ......}voidg(){ ......
gotoL;//Error ......}名空间作用域对于一个多文件构成的程序,有时会面临一个问题:在一个源文件中要用到两个分别在另外两个源文件中定义的不同全局程序实体(如:全局函数),而这两个全局程序实体的名字相同。C++提供了名空间(namespace)设施来解决上述的名冲突问题。在一个名空间中定义的全局标识符,其作用域为该名空间。当在一个名空间外部需要使用该名空间中定义的全局标识符时,可用该名空间的名字来修饰或受限。3、//模块2namespaceB{ intx=0; voidf() {...... }}...A::x...//A中的xA::f();//A中的f...B::x...//B中的xB::f();//B中的fusingnamespaceA;...x...//A中的xf();//A中的f...B::x...//B中的xB::f();//B中的fusingA::f;...A::x...//A中的x
f();//A中的f...B::x...//B中的xB::f();//B中的f//模块31、2、递归函数函数的调用是可以嵌套的。如果一个函数在其函数体中直接或间接地调用了自己,则该函数称为递归函数。
直接递归voidf(){..........f()..........}间接递归externvoidg();voidf(){..........g()..........}voidg(){.........f().........}递归函数的作用在程序设计中经常需要实现重复性的操作。循环为实现重复操作提供了一种途径。实现重复操作的另一个途径是采用递归函数。“分而治之”(DivideandConquer)设计方法:把一个问题分解成若干个子问题,而每个子问题的性质与原问题相同,只是在规模上比原问题要小。每个子问题的求解过程可以采用与原问题相同的方式来进行。递归函数为上述设计方法提供了一种自然、简洁的实现机制例:求第n个fibonacci
数(递归解法)int
fib(intn){ if(n==1) return0; elseif(n==2) return1; else returnfib(n-2)+fib(n-1);}递归函数的执行过程//用递归函数求n!
int
f(intn){ if(n==0) return1; else returnn*f(n-1);}递归条件和结束条件在定义递归函数时,一定要对两种情况给出描述:递归条件。指出何时进行递归调用,它描述了问题求解的一般情况,包括:分解和综合过程。结束条件。指出何时不需递归调用,它描述了问题求解的特殊情况或基本情况例:解汉诺塔问题
汉诺塔问题:有A,B,C三个柱子,柱子A上穿有n个大小不同的圆盘,大盘在下,小盘在上。现要把柱子A上的所有圆盘移到柱子B上,要求每次只能移动一个圆盘,且大盘不能放在小盘上,移动时可借助柱子C。编写一个C++函数给出移动步骤,如:n=3时,移动步骤为:A
B,A
C,B
C,A
B,C
A,C
B,A
B。
ABC当n=1时,只要把圆盘从A移至B就可以了(输出:A
B)。而当n大于1时,我们可以把该问题分解成下面的三个子问题:把n-1个圆盘从柱子A移到柱子C。把第n个圆盘从柱子A移到柱子B。把n-1个圆盘从柱子C移到柱子B。上面的子问题1和3与原问题相同,只是盘子的个数少了一个;子问题2是移动一个盘子的简单问题。#include<iostream>usingnamespacestd;voidhanoi(char
x,char
y,char
z,intn)//把n个圆盘从x表示的
//柱子移至y所表示的柱子。{ if(n==1)
cout<<"1:"<<x<<"→"<<y<<endl;//把第1个
//盘子从x表示的柱子移至y所表示的柱子。
else { hanoi(x,z,y,n-1);//把n-1个圆盘从x表示的柱子移至
//z所表示的柱子。
cout<<n<<":"<<x<<"→"<<y<<endl; //把第n个圆盘从x表示的柱子移至y所表示的柱子。
hanoi(z,y,x,n-1);//把n-1个圆盘从z表示的柱子移至
//y所表示的柱子。
}}递归与循环的选择对于一些递归定义的问题,用递归函数来解决会显得比较自然和简洁,而用循环来解决这样的问题,有时会很复杂,不易设计和理解。在实现数据的操作上,它们有一点不同:循环是在同一组变量上进行重复操作(循环常常又称为迭代)递归则是在不同的变量组(属于递归函数的不同实例)上进行重复操作。递归的缺陷:由于递归表达的重复操作是通过函数调用来实现的,而函数调用是需要开销的;栈空间的大小也会限制递归的深度。递归算法有时会出现重复计算。函数名重载对于一些功能相同、参数类型或个数不同的函数,有时给它们取相同的名字会带来使用上的方便。例如,把下面的函数:voidprint_int(inti){......}voidprint_double(doubled){......}voidprint_char(charc){......}voidprint_A(Aa){......}//A为自定义类型定义为:voidprint(inti){......}voidprint(doubled){......}voidprint(charc){......}voidprint(Aa){......}上述的函数定义形式称为函数名重载。对重载函数调用的绑定确定一个对重载函数的调用对应着哪一个重载函数定义的过程称为绑定(binding,又称定联、联编、捆绑)。在编译时刻由编译程序根据实参与形参的匹配情况来决定。从形参个数与实参个数相同的重载函数中按下面的规则选择一个:精确匹配提升匹配标准转换匹配自定义转换匹配匹配失败精确匹配类型相同对实参进行微小的类型转换:数组变量名->数组首地址函数名->函数首地址等等例如,对于下面的重载函数定义:
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);print(0);绑定失败带缺省值的形式参数在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++提供了两种解决上述问题的办法:宏定义内联函数宏定义在C++中,利用一种编译预处理命令:宏定义可以实现类似函数的功能:#define凵<宏名>(<参数表>)凵<文字串>例如:#define凵max(a,b)凵(((a)>(b))?(a):(b))cout<<max(x,y);宏定义是文字替换!宏定义的不足之处有时会出现重复计算。例如:#define凵max(a,b)凵(((a)>(b))?(a):(b))max(x+1,y*2)将被替换成:(((x+1)>(y*2))?(x+1):(y*2))不进行参数类型检查和转换。
不利于一些工具对程序的处理。
内联函数C++提供了另外一种解决函数调用效率不高问题的设施:内联函数。内联函数是指在函数定义中,可以在函数返回类型之前加上一个关键词inline,例如:inlineint
max(inta,intb){ returna>b?a:b;}内联函数的作用是建议编译程序把该函数的函数体展开到调用点。内联函数形式上属于函数,它遵循函数的一些规定,如:参数类型检查与转换。使用内联函数时应注意以下几点:编译程序对内联函数的限制。内联函数名具有文件作用域。编译预处理命令C++程序中可以写一些供编译程序使用的命令:编译预处理命令。编译预处理命令不是C++程序所要完成的功能,而是用于对编译过程给出指导,其功能由编译预处理系统来完成。编译预处理命令主要有:文件包含命令(#include)宏定义(#define)命令条件编译命令条件编译编译程序根据不同的情况来选择需编译的程序代码。条件编译的作用:基于多环境的程序编制程序调试基于多环境的程序编制#ifdefUNIX ......//适合于UNIX环境的代码#else ......//适合于其它环境的代码#endif......//适合于各种环境的代码程序调试加入调试信息#ifdefDEBUG......//调试信息#endif利用标准库#include<cassert>//或<assert.h>......assert(x==1);条件编译命令的常用格式#ifdef/#ifndef<宏名> <程序
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2024-2030年消防车吸水管市场竞争格局及未来发展供给预测分析报告(-版)
- 2024-2030年海上动力船行业市场现状供需分析及投资评估规划分析研究报告
- 2024-2030年泡沫包装制品市场发展现状调查及供需格局分析预测报告
- 2024-2030年河道整治产业市场发展分析及发展趋势与投资研究报告
- 2024-2030年汽车融资租赁行业市场运行分析及竞争形势与投资研究报告
- 2024-2030年江苏省小微金融行业市场深度调研及发展趋势与投资前景研究报告
- 2024-2030年水果店行业风险投资发展分析及运作模式与投资融资研究报告
- 2024-2030年氢内燃机行业竞争策略及未来供需平衡预测研究报告
- 2024-2030年植物藻类提取物行业市场深度分析及发展前景与投资机会研究报告
- 2024-2030年木炭烤架行业市场现状供需分析及投资评估规划分析研究报告
- 最新经典住宿清单(流水账单)模版
- 出生证明日文翻译
- 全国专业标准化技术委员会目录
- 中国铁路总公司公开招聘报名表doc.doc
- 隧道台车计算书
- 工伤风险数据库
- 试生产现场安全检查表
- 建筑工程总承包与分包界面划分大全(4种)
- 物资管理相关流程图
- 高速公路黄河特大桥施工渡汛方案
- 历史故事课件指鹿为马图文.ppt
评论
0/150
提交评论