C++函数和函数模板详解.ppt_第1页
C++函数和函数模板详解.ppt_第2页
C++函数和函数模板详解.ppt_第3页
C++函数和函数模板详解.ppt_第4页
C++函数和函数模板详解.ppt_第5页
已阅读5页,还剩91页未读 继续免费阅读

下载本文档

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

文档简介

1、第3章 函数和函数模板,C+语言的模块设计离不开函数,函数设计更离不开参数。在面向对象程序设计中,成员函数也是函数,只是它们的类型及其返回值更复杂些。由此可见,熟练地掌握函数知识,是非常必要的。 掌握函数设计和调用的正确方法,是程序设计的基本功。正确设计函数原型和参数类型,不仅能保证函数的正确性,而且能提高程序设计的效率。 本章除介绍函数调用、递归调用、以及函数调用中的参数替换和返回值等问题之外,还将结合软件编程技术的发展,讨论函数指针、内联函数、函数重载、函数模板及算法知识等,但不涉及类的构造函数和成员函数。,3.1 函数基本要素 3.2 函数调用形式 3.3 函数参数的传递方式 3.4 深

2、入讨论函数返回值 3.5 内联函数 3.6 函数重载和默认参数 3.7 函数模板,主要内容,3.1 函数基本要素 1. 函数值和return语句 一般情况下,函数必须返回与函数声明相一致的值作为函数的值。函数返回使用如下形式实现: return (表达式); 称return为返回语句。这里的表达式的值就是要返回的函数值。表达式两边的圆括号可有可无。return语句在一个函数里可以多次使用,但返回值的类型必须一致且与函数的类型说明一致。当函数不需要带值返回时,将函数直接定义为void类型,最为方便。这样就不必在函数体内使用return语句。 实际上,最后一个大括号“”有return语句的作用(函

3、数末尾隐含有一个return语句)。 若函数不带值返回,实际上并不是不返回什么值,而是返回一个不定值。,因为不考虑使用,所以尽管返回的是不定值,也就没有多大关系了。也可以在函数体内使用不带表达式的return语句,即 return; 【例3.1】作为认识返回值的练习,写一个具有两个string类型参数的函数max,比较这两个参数(字符串)的大小,并把大者作为函数的返回值。 下面的主函数调用max函数,执行该程序,str的值就等于str1、str2中大的一个。为了分析方便,使用注释为程序语句编号。 #include /1 #include /2 using namespace std; /3 s

4、tring max(string,string); /4 void Welcome(); /5 void main( ) /6, /7 Welcome(); /8 string str1(abc),str2(abC), str; /9 str=max (str1, str2); /10 cout s2 ) return s1; /15 else return s2; /16 /17 void Welcome() /18 cout欢迎使用string类。endl; /19,2. 函数原型声明 第4行是函数max的原型声明,编译系统在编译到第10行时,会检查max的参数及返回值类型是否与函数声明一

5、致。编译到函数的定义语句第13行时,还将检查max的定义是否与事先声明的一致。如果发生不一致的情况,编译系统就要给出警告。 第5行是函数Welcome的原型声明,这个函数既没有参数,也没有返回值。 应养成将所有函数集中进行函数原型声明的习惯。一般可以声明在头文件中,或者在主函数之前。,3. 工作过程说明 这个程序是由两个函数,即主函数main和函数max组成的,并且main函数调用max函数(见第10行)。 被调用函数max里的参数s1和s2是形参(见第13行)。在调用max函数时,形参要被实参替换,如第10行所示,形参s1和s2被实参str1和str2替换。实参不能变化,如第9行所示,str

6、1、str2分别被赋值为abc和abC。 在main函数中调用max函数(即执行第10行)时,实际是执行1317行的内容。 max函数里使用两个return语句,用来把max函数里所求的最大值,作为max函数的返回值赋值给main函数里面的对象变量str。,4. 函数体内的变量为局部变量 可以将函数max的定义改写如下: string max(string s1, string s2) string str; if (s1 s2 ) str=s1; else str=s2; return str; main函数内部和max函数内都有变量str,两个变量str都是在各自的“”内声明的变量,这样的

7、变量叫做局部变量。虽然它们使用了相同的变量名,但互不影响。,3.2 函数调用形式 函数的调用方法是在表达式、语句或参数中直接写出函数名,并用实参代替形参。一旦出现被调用的函数,就要转去执行这个函数,并将该函数执行结果返回来。根据被调用函数在源程序中出现的位置,把函数调用分为语句调用、表达式调用和参数调用3种形式。 3.2.1 函数语句调用 被调用函数作为一个独立的语句出现在源程序中,这种语句就叫函数语句。这种调用很简单,只要把被调用函数的函数名直接写出来,并以实参替换原来的形参即可。函数调用也可以嵌套,且与函数的编写顺序没有关系。【例3.1】中第8行的语句“Welcome();”就是函数语句调

8、用,这种被调用的函数没有返回值。,3.2.2 函数表达式调用 被调用函数出现在一个表达式中,这种表达式也可叫做函数表达式,它用在被调用函数有返回值的情况。【例3.1】中的语句“str=max (str1,str2 );”,就是函数表达式调用。 3.2.3 函数参数调用 被调用函数作为函数的一个参数(函数参数)出现。这种调用虽然形式上不同于函数表达式调用,但调用条件和注意事项与函数表达式调用是相同的。 【例3.2】函数参数调用的例子。 #include #include using namespace std; string max(string,string);,void main( ) st

9、ring str1(abc),str2(abC), str3(b),str; str=max (str1, max(str2,str3); cout s2 ) return s1; else return s2; ,程序先调用max求str2和str3中的大者(b),再将这个返回值作为max函数的一个参数,求“b”与另一个数str1的大者,输出“str=b”。即max(str2,str3)作为函数max的参数。调用函数时,参数替换的实质是把实参数值赋给形参变量,这叫做传值。 3.2.4 递归调用 函数调用,一般是一个函数调用另外一个函数。此外,函数还可以自己调用自已,这种调用叫做函数的递归调用

10、。递归调用有两种方式,一种是直接调用其本身,另一种是通过其他函数间接地调用。在这里只给出直接递归调用的例子。 【例3.3】 求阶乘的递归调用程序。 #include using namespace std; int factorial(int);,void main( ) int n; coutn; coutn!=factorial(n)endl; int factorial( int x ) if ( x=0 ) return 1; else return ( x*factorial( x-1 ) ); 运行输入:Input n=5 显示结果:5!=120,main函数调用factorial

11、函数。其中factorial有返回值,函数递归调用时,每调用一次其自动型变量就占据堆栈一个区域,供各自的调用使用。递归调用在堆栈中临时占据的存储区域是较多的,在实际运行时,递归调用的时间效率较差。 递归算法在可计算性理论中占有重要地位,它是算法设计的有力工具,对于拓展编程思路非常有用。就递归算法而言并不涉及高深数学知识,只不过初学者要建立起递归概念不十分容易。为了建立帮助建立递归的概念,下面以求3!为例,给程序编号,以便分析程序的执行过程。图3.1用图解的方法分解执行过程。,int factorial( int x ) / / if ( x=0 ) return 1; / else retur

12、n ( x*factorial( x-1 ) ); / ,下面结合图3.1,说明执行factorial( 3 )的递归调用过程。 (1) 第1次执行factorial( 3 ),当执行到时,递归 调用factorial( 3-2 )。 (2)执行factorial( 2 ),当执行到时,递归调用factorial( 2-1 ) 执行factorial( 1 ),满足语句,经结束本次 调用并返回1。 (4) 返回继续执行factorial( 2 )的语句,执行return 2*1,结束factorial( 2 )。 (5) factorial( 2 )的返回值赋给factorial( 3 )的语

13、句,执行return 3*2*1。 (6) 结束factorial( 3 )的执行,返回值为3*2*1=6。,由此可见,欲求 factorial(3),先要求 factorial(2);要求 factorial(2) 先求 factorial(1)。就象剥一颗圆白菜,从外向里,一层层剥下来,到了菜心,遇到 1 的阶乘,其值为1,到达了递归的边界。然后再用 factorial(2)=2*factorial(1) 得到factorial(2),再用factorial(2) 的值和公式 factorial(3)=3*factorial(2) 求得factorial(3)。这个过程是使用 factor

14、ial(n)=n*factorial(n-1 ) 这个普遍公式,从里向外倒推回去得到 factorial( n ) 的值。,3.2.5 递归与递推的比较 递推是计算机数值计算中的一个重要算法。思路是通过数学推导,将复杂的运算化解为若干重复的简单运算,以充分发挥计算机长于重复处理的特点。 1. 递推数列的定义 一个数列从某一项起,它的任何一项都可以用它前面的若干项来确定,这样的数列称为递推数列,表示某项与其前面的若干项的关系就称为递推公式。例如自然数1,2,n的阶乘就可以形成如下数列: 1!,2!,3!,(n-1)!,n! 令fact(n)为n的阶乘,依据后项与前项的关系可写出递推公式 fact

15、(n)=n* fact(n-1) (通项公式) fact(1)=1 (边界条件),2. 递推算法的程序实现 在有了通项公式和边界条件后,采用循环结构,从边界条件出发,利用通项公式通过若干步递推过程就可以求出解来。 以求阶乘为例。递推是从已知的初始条件出发,逐次去求所需要的阶乘值。 【例3.4】求5的阶乘值。 求5!的初始条件fact(1)=1,于是可推得 fact(2)=2* fact(1)=2*1=2 fact(3)=3* fact(2)=3*2=6 fact(4)=4* fact(3)=4*6=24 fact(5)=5* fact(4)=5*24=120 递推过程相当于从菜心“推到”外层。

16、,/递推求阶乘参考程序 #include using namespace std; void main() int sum=1,i=0; for(i=2; i=5; i+) sum=i*sum; cout5!=sumendl; ,3. 递推与递归区别 递推过程相当于从菜心“推到”外层。递归算法的出发点并不放在初始条件上,而放在求解的目标上,从所求的未知项出发逐次调用本身的求解过程,直到递归的边界(即初始条件)。就求阶乘的例子而言,读者会认为递归算法可能是多余的,费力不讨好。但许多实际问题不可能或不容易找到显而易见的递推关系,这时递归算法就表现出了明显的优越性。它比较符合人的思维方式,逻辑性强,

17、可将问题描述得简单扼要,具有良好的可读性,易于理解。许多看来相当复杂,或难以下手的问题,如果能够使用递归算法,就会使问题变得易于处理。 【例3.5】A、B、C、D、E合伙夜间捕鱼,凌晨时都疲惫不堪,各自在河边的树丛中找地方睡着了。日上三竿,A第一个醒来,他将鱼平分作5份,把多余的一条扔回湖中,拿自己的一份回家去了;B第二个醒来,也将鱼平分作5份,扔掉多余的一条,只拿走自己的一份;接着C、D、E依次醒来,也都按同样的办法分鱼。问5人至少合伙捕到多少条鱼?每个人醒来后看到的鱼数是多少条?,使用递推的解题思路如下: 假定A、B、C、D、E 5人的编号分别为1、2、3、4、5,为了容易理解,让整数数组

18、的标号直接与这5个人的序号对应,定义整数数组fish6。不使用fish0,从而可以使用数组fishk表示第k个人所看到的鱼数。fish1表示A所看到的鱼数, fish2表示B所看到的鱼数。显然有如下关系: fish1= 5人合伙捕鱼的总鱼数 fish2= (fish1-1)*4/5 fish3= (fish2-1)*4/5 fish4= (fish3-1)*4/5 fish5= (fish4-1)*4/5 由此可以写出如下的一般表达式: fishi= (fishi-1-1)*4/5 i=2,3,4,5,这个公式可用于从已知A看到的鱼数去推算B看到的,再推算C看到的,。现在要求的是A看到的,能否

19、倒过来,先知E看到的再反推D看到的,直到A看到的。为此将上式改写为 fishi-1=fishi*5/4+1 i=5,4,3,2 分析上式如下: 当i=5时,fish5表示E醒来后看到的鱼数,该数应满足被5整除后余1,即fish5%5=1 当i=5时, fishi-1 表示D醒来后看到的鱼数,该数既要满足fish4=fish5*5/4+1,又要满足fish4%5=1。显然,fish4不能不是整数,这个结论通样可以用于fish3,fish2,和fish1。, 按题意要求5人合伙捕到的最少鱼数,可以从小往大枚举,可先让E所看到的鱼数最少为6条,即fish5初始化为6来试,之后每次增加5再试,直至递推

20、到fish1且所得整数除以5之后的鱼数为1。根据上述思路,可以将程序分为3个部分:程序准备(包括声明和初始化)部分、递推部分和输出结果部分。程序准备部分包含定义数组fish6并初始化为1,定义循环控制变量i并初始化为0。输出结果部分就是输出计算结果。以上两个部分都很简单,下面着重介绍递推部分的实现方法。 递推部分使用do while直到型循环结构,其循环体又含两块: (1) 枚举过程中的fish5的初值设置,一开始fish5=1+5;以后每次增5。也就是说,第1个边界条件是fish=6,以后的边界条件是每次递增5。,(2) 使用一个for循环,i的初值为4,终值为1,步长为-1,该循环的循环体

21、是一个分支语句,如果fishi+1不能被4整除,则跳出for循环(使用break语句);否则,从fishi+1算出fishi。当由break语句让程序退出循环时,意味着某人看到的鱼数不是整数,当然不是所求,必须令fish5加5后再试,即重新进入直到型循环do while的循环体。当正常退出for循环时,一定是循环控制变量i从初值4,一步一步执行到终值1,每一步的鱼数均为整数;最后i=0,表示计算完毕,且也达到了退出直到型循环的条件。 /捕鱼问题参考程序 #include /预编译命令 using namespace std; void main() int fish6=1,1,1,1,1,1;

22、 /记录每人醒来后 /看到的鱼数,int i=0; do fish5=fish5+5; /让E看到的鱼数增5 for (i=4; i=1; i-) if (fishi+1%4!=0) break; /跳出for循环 else fishi=fishi+1*5/4+1; /计算第i人 /看到的鱼数 while (i=1); /当i=1,继续做do循环,/输出计算结果 for (i=1; i=5; i+) cout“第”i“个人看到的鱼是” fishi条。endl; 程序运行的输出结果如下: 第1个人看到的鱼是3121条。 第2个人看到的鱼是2496条。 第3个人看到的鱼是1996条。 第4个人看到

23、的鱼是1596条。 第5个人看到的鱼是1276条。,3.3 函数参数的传递方式 函数参数的传递方式有3种:传值、传地址和传引用。函数的参数还可以设计成默认形式,以方便使用。 3.3.1 传值 传值是将实参的值传递给形参,形参拥有实参的一个备份,当在函数中改变形参的值时,改变的是这个备份中的值,不会影响原来实参的值。传值方式可以防止被调用函数改变参数的原始值,这在很多场合是很重要的。 著名的swap函数充分地说明了传值和传址的区别。以交换两个整数为例,下面是传值的swap函数和测试程序:,【例3.6】传值不会改变原来值的例子。 #include #include using namespace

24、std; void swap(string, string); /函数参数采用传值方式 void main() string str1(现在),str2(过去); swap(str1, str2); /传值 cout返回后:str1=str1 str2=str2endl; ,void swap(string s1, string s2) string temp=s1; s1=s2; s2=temp; cout交换为:str1=s1 str2=s2endl; 虽然swap函数内交换str1和str2的值,但不影响原来的值,所以程序输出结果如下: 交换为:str1=过去 str2=现在 返回后:s

25、tr1=现在 str2=过去 直接使用一般的数据类型的对象,或者使用类和结构的对象作为参数,均是传值方式。注意,数组不能使用传值方式。,3.3.2 传地址 传地址又简称传址,它传递的是指向参数的指针。实际上,形参传递的就是实参本身,当在函数中改变形参的值时,改变的就是原来实参的值。大多数程序设计者为提高运行效率,常使用传址方式。例如传递类或结构时,传递对象的地址。指针可以指向对象的地址,所以传址要用到指针。数组名就是指针名,所以数组只能用传址方式。 【例3.7】传地址改变原来值的例子。 #include #include using namespace std; void swap(strin

26、g*, string*); /函数参数采用传址方式 void main(), string str1(现在),str2(过去); swap( 因为实参与形参的地址相同,所以改变形参就是改变实参。输出结果如下: 交换为 str1=过去 str2=现在 返回后 str1=过去 str2=现在,注意:不要一定要在主程序里产生指针,然后再用指针作为参数。函数原型参数的类型是指针,可以直接让它指向对象地址,即 string *s1= ,void swap(int a) int temp=a0; a0=a1; a1=temp; cout交换为: a=a0 b=a1endl; 运行结果如下: 交换为:a=8

27、 b=3 返回后:a=8 b=3,3.3.3 传引用方式 C+提供引用,主要是用来建立函数参数的引用传递方式。在说明引用参数时,不需提供初始值,其初始值在函数调用时由实参提供。传引用是将对象的引用作为参数传递,引用和被引用对象的地址一样,所以改变形参就是改变实参。 【例3.9】通过传引用改变原来值的例子。 #include #include using namespace std; void swap(string /函数参数采用 /传引用方式,void main() string str1(现在),str2(过去); swap(str1, str2); /传引用 cout返回后:str1=s

28、tr1 str2=str2endl; void swap(string ,当程序中调用函数swap时,实参str1和str2分别初始化引用s1和s2,所以在函数swap中,s1和s2分别引用str1和str2,对s1和s2的访问就是对str1和str2的访问,所以函数swap改变了main 函数中变量str1和str2的值。通过使用引用参数,一个函数可以修改另一个函数内的变量。 程序输出如下: 交换为:str1=过去 str2=现在 返回后:str1=过去 str2=现在 也可以改为传递结构对象的引用,其效果一样。如果使用数组,参见【例1.7】间接引用数组的方法。 因为传引用比传指针更好,所以

29、C+建议使用传引用的方式。,【例3.10】 求10个学生成绩的平均值,并统计其中不及格的人数。要求用一个函数实现,并返回这两个数据给调用函数,并且函数的形参使用引用来实现。 #include using namespace std; typedef double array12; void avecount(array ,bn-2=ave/(n-2); /填入平均成绩 bn-1=count; /填入不及格人数 void main() array b=12,34,56,78,90,98,76,85,64,43; array 程序输出:平均成绩为61.8分,不及格人数有5人。,3.3.4 默认参数

30、 C+语言在函数调用时,引进了一种新类型的参数:默认参数。默认参数就是不要求程序员设定该参数,而由编译器在需要时给该参数赋默认值。当程序员需要传递特殊值时,必须显式地指明。默认参数是在函数原型中说明的。默认参数可以多于1个,但必须放在参数序列的后部,例如: int SaveName( char *first, char *second = , char *third = ,char *fourth = ); 这种方式表明在实际调用函数SaveName时,如果不给出参数second、third和fourth,则取默认值。如果一个默认参数需要指明一个特定值,则在其之前的所有参数都必须赋值。在上例中

31、,如果需给出参数third的值,则必须同时也给first和second赋值,例如: int status=SaveName(Alpha, Bravo, Charlie);,【例3.11】设计一个根据参数数量输出信息的函数。 #include #include using namespace std; void Display(string s1, string s2=,string s3=); void main() string str1(现在),str2(过去),str3(将来); Display(str1); Display(str1,str2,str3); Display(str3,s

32、tr1); Display(str2,str3); ,void Display(string s1, string s2, string s3) if(s2= 输出结果如下: 现在 现在、过去、将来 将来、现在 过去、将来,3.3.5 使用const保护数据 用const修饰传递参数,意思是通知函数,它只能使用参数而无权修改它。这主要是为了提高系统的自身安全。C+中普遍采用这种方法。 【例3.12】不允许改变作为参数传递的字符串内容的实例 #include #include using namespace std; void change(const string ,void change(c

33、onst string 参数传递使用const修饰,程序输出如下: Can you change it? No! /函数内不能修改,只能使用 Can you change it? / 原内容不变,3.4 深入讨论函数返回值 C+函数的返回值类型可以是除数组和函数以外的任何类型。定义一个函数,要正确选择函数返回值。非void类型的函数必须向调用者返回一个值。该值可以是标量对象,也可以是复合对象。标量对象指那些声明为基本类型的对象,返回值置于寄存器中。数组只能返回地址。复合对象是指结构或类的对象。返回类对象的情况将在第5章介绍。返回结构类型对象时,是逐个字节地拷贝过来的。 当函数返回值是指针或引用

34、对象时,需要特别注意:函数返回所指的对象必须继续存在,因此不能将函数内的局部对象作为函数的返回值。 虽然在C+中允许返回一个指向局部静态对象的指针或引用,但最好不要这样做。,3.4.1 返回引用的函数 函数可以返回一个引用,将函数说明为返回一个引用的主要目的是为了将该函数用在赋值运算符的左边。函数原型的表示方法为: 数据类型 / 输出16 ,int /返回局部对象 由于局部对象的生存期仅局限于函数内部,当函数返回时,局部对象就消失了,因此,上述函数返回一个无效的引用。,3.4.2 返回指针的函数 函数的返回值可以是存储某种类型数据的内存地址(例如变量的地址、数组的首地址及指针变量的地址),称这

35、种函数为指针函数。它们的一般定义形式为: 类型标识符 *函数名(参数列表); 【例3.14】 使用函数input 输入一组数并返回一个指针,然后由主函数main将这组数显示出来的例子。 #include using namespace std; float *input( int /声明指针,data=input(num); /调用函数,返回指针赋给 data if(data) /data不空,输出所指内容 for( int i=0; i n; if(n=0) return NULL; /输入个数不合理则退出 float *buf=new floatn; /根据输入数据数量申请空间,if(bu

36、f=0) return NULL; /没申请到则退出 for(int i=0;i bufi; return buf; /返回指针 程序运行示范如下: Input number:3 12.25 45.32 78.94 12.25 45.32 78.94 函数input用于接收用户输入,数据为float类型,它首先提示用户输入需输入的数据的个数,然后分配一块存储区域来保存用户输入的数据。若函数在运行时返回一个有效的指针,则这个指针指向的内存供用户输入数据,数据的个数可从input的引用参数中得知。,输入的数据个数小于或等于零,或内存分配失败时,函数都返回一个空指针值,这个值可作为程序异常的标志。因

37、为在C+中,除了内存分配失败之外,new不会返回空指针,并且没有任何对象的地址值为零。 调用input函数时,需要一个和该函数返回类型一致的指针对象,以便保存input函数返回的指针,并在判断这个指针值非零的情况下,使用这个指针所指向的内存中的数据。另外,调用input函数的函数应负责释放由input函数分配的内存。 一般说来,指针所指向的对象的生存期不应低于该指针的生存期。,3.5.3 返回对象的函数 【例3.15】函数返回对象的例子。 #include #include using namespace std; string input(const int); /声明返回string类型的

38、函数 void main( ) int n; coutn; /接收要处理的字符串数量 string str=input(n); /将函数返回的对象赋给对象str coutstrendl; ,string input(const int n) string s1, s2; /建立两个string类的对象(均为空串) for (int i=0;is1; s2=s2+s1+ ; /将接收的字符串相加 return s2; /返回string对象 函数input用来接收字符串对象,然后将string的对象s2作为返回值。运行演示如下: Input n=3 zhang san feng zhang sa

39、n feng,3.5.4 函数返回值作为参数 如果用函数返回值作为另一个函数的参数,这个返回值必须与参数的类型一致。可以通过函数指针将函数作为参数传给其他函数,以便能够创造出功能很强的高级函数系统。 与C语言一样,函数在编译时,它的源代码转换成了目标代码,同时确定了函数的入口地址。程序调用函数,也就是指向了这个入口地址。因而,指向函数的指针实际上包含了函数的入口地址,所以赋给指针的地址就是函数的入口地址,从而该指针就用来代替函数名。这就使得函数可以作为实参传递给其他函数。由此可见,可以将一个指向函数的指针传递给函数,或放置在数组中提供给其他对象使用。 函数指针定义形式如下: 数据类型标识符 (

40、*指针对象名) (函数参数的数据类型列表);,声明函数指针时,只需要给出函数参数的数据类型,不需要给出参数名。如果给出也可以,只不过编译系统不理睬参数名而已。 下面的例子是用指向函数的函数指针对象作为参数,从而实现函数地址的传递(也就是将函数名作为实参),达到将函数作为参数传给其他函数的目的。 假设已经有分别求两个数的大者、小者及平均值的3个函数max,min和mean。现在另外定义一个函数 all如下: int all(int x, int y, int (*func)(int,int) return (*func)(x,y); ,函数all共有3个形参,有2个int形参,1个为指向函数的指

41、针对象func。这个对象声明为 int (*func)(int,int ),可用一个定义的函数代替all中的func,例如all(a,b,mean)相当于执行mean(a,b),从而输出a和b的平均值。同理,可用相同方法调用min及max,而all函数的形式一样,只是在调用时改变实参函数名而已。这就增加了函数使用的灵活性,它在大型程序设计,尤其是模块设计时特别有用。 【例3.16】完整的示例程序。 #include using namespace std; int all (int, int, int (*)(int,int); /含有函数指针的 /函数原型声明 int max(int,int

42、 ),min(int,int ),mean(int,int ); /函数原型 /声明 void main( ), int a, b; cinab; couty)?x:y; int min(int x, int y) return (xy)?x:y; int mean(int x, int y) return( (x+y)/2 );,输入58 62 输出 max=62 min=58 mean=60 【例3.17】求函数10 x2-9x+2在区间 0, 1内x以0.01的增量变化的最小值。 #include using namespace std; double const s1=0.0; dou

43、ble const s2=1.0; double const step=0.01; double func(double); double value(double(*)( double);,void main ( ) double (*p)(double); p=func; /指向目标函数 cout最小值是:value(p)endl; double func(double x) /目标函数 return (10*x*x-9*x+2); double value(double(*f)(double) /定义求最小值函数, /它包括函数指针 double x=s1, y=(*f)(x);,whi

44、le( x (*f)(x) ) y=(*f)(x); x += step; return y; 运行结果: 最小值是:- 0.025 函数指针的目标函数必须已经存在,才能被引用。本例中p函数指针的目标函数func已经在p被调用之前声明,也已经通过语句 p=func; 指向它的目标函数func。,3.6 内联函数 下面的程序使用isnumber来判定一个输入字符是否属于数字字符,程序如下: #include using namespace std; int isnumber(char c) return ( c = 0 .,函数isnumber很简单,但很有用,在许多程序中都要用到,但对于这样一

45、个简单的函数,使用函数调用(尤其是程序中多次调用这个函数时)却大大降低了使用效率。为了提高效率,常常将函数main中对函数isnumber的调用替换成表达式,即 if ( (c=0 这种替换用手工来做是很繁琐的,可以让C+编译程序来做, 这只要在函数isnumber的定义前加上关键字inline即可,即定义为: inline int isnumber (char c) C+编译器在遇到对函数isnumber 调用的地方都用这个函数体替换该调用表达式。使用关键字inline说明的函数称内联函数。在C+中,除具有循环语句、switch语句的函数不能说明为内联函数外(编译器会对这类错误给出警告信息)

46、,其他函数都可以说明为内联函数。 ,使用内联函数,加快了程序执行速度,但如果函数体语句多,则会增加程序代码的大小。使用小的内联函数在代码速度和大小上可以取得折衷,其他情况下取决于程序员是追求代码速度,还是追求代码的规模。 由于编译器必须知道内联函数的函数体,才能进行内联替换,因此,内联函数必须在程序中第一次调用此函数的语句出现之前被编译器看见。例如,上面的程序可采用下述方式。 #include using namespace std; inline int isnumber(char c) return ( c = 0 void main( )/ . ,3.6 函数重载和默认参数 C+允许为同

47、一个函数定义几个版本,这称为函数重载。函数重载使一个函数名具有多种功能,即具有“多种形态”,称这种形态为多态性。在下面的例子中,为求两个数值中的最大者,设计一个名为max的函数,并为不同参数类型各设计一个函数体。同理,也可用这个函数名设计一个具有3个整型参数,从而求出3个整数最大值的函数体。当然,也可以为具有3个参数的max函数设计不同参数类型的函数体。 【例3.18】函数重载产生多态性的例子。 #include using namespace std; double max(double,double); /2个实型参数的函数原型 int max(int,int); /2个整型参数的函数原型

48、 char max(char,char); /2个字符型参数的函数原型 int max(int,int,int); /3个整型参数的函数原型,void main( ) coutm2)?m1:m2; int max(int m1, int m2) return(m1m2)?m1:m2; char max(char m1, char m2) return(m1m2)?m1:m2;,int max(int m1, int m2, int m3) int t=max(m1,m2); return max(t,m3); C+能够正确调用相应函数,程序输出结果如下: 17.54 56 w max(5,9,

49、4)=9 max(5,4,9)=9,从函数原型可见,它们的区别一是参数类型不同,二是参数个数不同。这种功能与自然语言中对动词的使用类似,此时的动词等价于C+的函数。例如一个对象在现实生活中可以对它进行清洁、移动、分解、修理或油漆,但这并不包括全部可用的动词(例如销毁)。而且并不知道动作将发生在哪些对象上,所以这些动词仅仅代表了一般性的动作。例如,尽管移动铅笔和移动桌子的概念相似,但它们所需要的操作并不相同。一个人可以移动铅笔,可能需要几个人移动大桌子,知道作用的对象是铅笔还是桌子之后,就能将它们与一系列的特殊操作联系起来。C+语言也只有向具有多态性的函数传递一个实际对象时,该函数才能与多种可能

50、的函数中的一种联系起来。换句话说,源代码本身并不总是能够说明某部分的代码是怎样执行的。这说明C+的函数调用机制与ANSI C的不同。C+的多态性又被直观地称为“一个名字,多个函数”。源代码,只指明函数调用,而不说明具体调用哪个函数。编译器的这种连接方式称为动态联编或迟后联编。在动态联编中,直到程序运行时才能确定调用哪个函数。动态联编需要虚函数的支持,这将在虚函数中介绍。对【例3.18】而言,编译器在编译时,能根据源代码调用固定的函数标识符,然后由连接器接管这些标识符,并用物理地址代替它们,这就称为静态联编或先期联编。静态联编是在程序被编译时进行的。 同理,可以设计一个求整数之和的函数。不过,如

51、果要求4个整数之和,使用函数重载则需要编写3个函数。这时可编写一个具有默认参数的函数。,【例3.19】编写一个具有默认参数的函数。 #include using namespace std; int add(int m1=0, int m2=0, int m3=0, int m4=0) return m1+m2+m3+m4; void main() coutadd(1,3),add(1,3,5), add(1,3,5,7)endl; 程序输出结果为:4,9,16,使用默认参数,就不能对少于参数个数的函数进行重载。例如,这里不能重载具有3个整型参数的add函数,因为编译器决定不了是使用3个参数,

52、还是4个参数的add函数,只能对多于4个参数的add函数重载。另外,仅有函数返回值不同也是区分不了重载函数的。当使用默认参数设计类的构造函数时,要特别注意。,3.7 函数模板 如上一节所述,为了求两个数据的最大值,可以使用重载函数实现。 其实,它们都是逻辑功能完全一样的函数,而编制这些函数所提供的程序文本完全一样,其区别仅仅是处理的数据类型不同,是否可以使用一个统一的方法处理它们呢?,3.7.1 函数模板基础知识 1. 引入函数模板 以求两个数据的最大值为例,既然区别仅仅是处理的数据类型不同,就把它们的操作抽象成如下形式: Type max(Type m1, Type m2) return(m

53、1m2)?m1:m2; 这里Type并不是一种实际的数据类型,在这个函数实例化时,希望编译器能用实际的类型来替代它。由于函数在设计时没有使用实际的类型,而是使用虚拟的类型参数,故其灵活性得到加强。当用实际的类型来实例化这种函数时,就好像照模板来制造新的函数一样,所以称这种函数为函数模板。将函数模板与某个具体数据类型连用,就产生了模板函数,又称这个过程为函数模板实例化,这种形式就是类型参数化。,在程序设计时并不给出相应数据的实际类型,而在实际编译时,方由编译器利用实际的类型给予实例化,使它满足需要。由此可见,可使编译器成为一种在函数模板引导下,制作符合要求的代码的程序设计辅助工具。 上面只是一种

54、想法,还不能实现。问题是如何知道定义的函数max是模板;函数里使用的Type如何与模板发生关系。 规定模板以template关键字和一个形参表开头。上面的函数虽然有两个参数,但参数类型一样,所以在尖括号里只需要说明一个类型参数的标识符,这样就可表示为: template Type max(Type m1, Type m2) return(m1m2)?m1:m2; 使用Type标识符来标识类型参数,即参数名是可以任意挑选的。尽管如此,字母T能使人联想到它是单词Type的第一个字母,所以一般都选T作为标识符。可把上面的函数重新改写为以下形式:,template T max(T m1, T m2)

55、return(m1m2)?m1:m2; class意为“用户定义的或固有的类型”。字母T标识这个模板有一个参数类型。当在程序中使用max(2,5)时,编译器能推断出这个T为int,并使用如下版本产生具体的模板函数: int max(int m1, int m2) return(m1m2)?m1:m2; 而max(2.5,8.8)则使用如下版本: double max(double m1, double m2) return(m1m2)?m1:m2; 由此可见,在调用函数模板时,函数参数的类型决定到底使用模板的哪个版本。也就是说,模板的参数是由函数推断出来的。,【例3.21】编制求两个数据中的最

56、大值的函数模板程序。 #include using namespace std; template T max(T m1, T m2) return(m1m2)?m1:m2; void main( ) cout(2,5)(2.0,5.) (w,a)(ABC,ABD)endl; 程序输出结果为:5 5 w ABD,【例3.22】编写具有模板类参数的重载函数实例。 #include #include #include using namespace std; void printer(complex); void printer(complex); void main() int i(0); co

57、mplex num1(2,3); complex num2(3.5, 4.5); printer(num1); printer(num2); ,void printer(complexa) string str1(real is ), str2=image is ; couta) string str1(real is ), str2=image is ; coutstr1a.real(),str2a.imag()endl; 【例3.23】使用类模板作为函数模板参数的程序。,#include #include #include using namespace std; template void printer(complexa) string str1(real is ), str2=image is ; coutstr1a.real(),str2a.imag()endl; 这里complex的参数已经类型参数化,所以功能比【例3.22】的重载函数强得多。使用上例的主程序,将得到同样结果。,2. 函数模板的参数 【例3.21】中

温馨提示

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

评论

0/150

提交评论