1、第6章 函 数第6章 函 数 6.1 函数的声明6.2 函数的参数传递6.3 函数的返回值6.4 函数名的过载/重载6.5 缺省的函数参数值6.6 递归6.7 参数数目可变的函数6.8 函数指针6.9 综合示例小结练习题 第6章 函 数本章要点:函数的基本概念及定义;函数的参数传递规则;函数的返回值;函数名的重载;缺省的函数参数值;递归函数;参数数目可变的函数;函数指针。第6章 函 数函数(Function)是支持面向过程程序设计范型最主要的机制。与函数相关的主要问题是函数的参数传递、函数的返回值和函数的调用规则等。从函数的内涵讲:函数是具有一定功能的一段代码的封装与抽象(Abstract)。
2、在阐述函数的各种问题细节之前,让我们首先思考这样一个问题:为什么要定义和使用函数?如果一段程序代码的功能是相对完整的,而这段代码在程序的多个地方都可以用到(即使有一些细节上的差别),就有理由考虑将这段代码抽象成一个函数,用不同参数(实参)来调用这个函数以体现上述差别。第6章 函 数这样做的意义不仅在于减少了程序代码的重复,还在于对程序进行了功能上的划分,便于代码的编制、理解和保持一致。程序设计语言的发明与发展史,实际上亦是人们抽象程度不断提高、发展的历史。计算机发明的早期,当函数这个概念与机制还没有诞生时,各种程序即使它们之间存在着许多共性,也几乎谈不到更无法进行任何重用(Reuse)。20世
3、纪60年代产生的Fortran语言第一次引入了函数的概念。在程序中引入函数(有的语言称为过程或子程序(Procedure)是软件技术发展历史上最重要的里程碑之一。它标志着软件模块化与软件重用的真正开始,Fortran语言也因其贡献而永远载入程序设计语言发展的史册!第6章 函 数第6章 函 数注意两段程序中的阴影部分,它们的代码形式虽然不同,但功能却是一样的,即完成两个整型量的交换。完成该功能的代码在程序中不止一次被用到(虽然存在着细微的差别),为了减少代码的编写量,亦为了程序的可重用性等,我们有必要将其抽象为一个函数:第6章 函 数第6章 函 数从上述实例可看出,定义并实现的swap函数不仅使
4、代码更加简洁,而且swap可被重用于其它需要相同功能的程序中。第6章 函 数6.1 函函数数的的声声明明一个函数的首部(函数体左花括号之前的东西)包括函数的返回类型、函数名及函数的参数表列,统称为函数的接口或界面,它表征了调用该函数所需要的信息。函数声明分为函数的(接口/原型)声明与函数的定义声明两部分。第6章 函 数6.1.1 函数接口函数接口/原型声明原型声明在函数接口的声明(以下简称为函数声明)中,必须给出函数的返回类型(如果有的话)、函数名,以及调用这个函数时所必须提供的参数的个数和类型(常称为参数表列)。注意:在函数声明中,可不给出函数参数(常称为形参,Formal Argument
5、s)的具体名字,因为编译器会忽略掉它,但为了程序的可读性,建议在函数声明时最好给出各参数确切的、有意义的参数名。例如下面一些语句:Elem*next_elem();char*strcpy(char*to,const char*from);void exit(int);都是函数声明语句。第6章 函 数6.1.2 函数的定义函数的定义一个函数调用就是跳转到函数的代码处而执行之。因此,在程序中调用的每个函数都必须在某个地方定义(且仅能定义一次)。一个函数定义即给出了函数体(以一对花括号为界)的函数声明。例如:第6章 函 数由于C+对任何用户标识符(函数名亦属于用户标识符)采用“先使用,后核实”的原则
6、,故一个函数若使用在前,定义在后,则在调用该函数前,必须对此函数进行(接口/原型)声明,且其声明必须与它所引用的函数定义的接口/原型完全相同(形参名可不同)。为了提高函数调用的效率,C+引入了另一种函数定义,即内联函数(inline)。inline是一个信号,它告诉编译器在编译时将对该在线函数的调用“在线化”,即将该在线函数的调用语句自动置换为该在线函数代码。因此,在执行函数调用时,就免去了函数调用中跳转、压栈与后续的弹栈动作,从而提高了函数调用的效率。第6章 函 数注意:inline描述符并不影响函数本身的语义。若需定义一个在线函数,仅需在函数定义中的函数返回类型前加关键字inline即可。
7、例如:inline int fac(int n)return(n2)?1:n*fac(n-1);即定义了一个在线函数。第6章 函 数6.2 函数的参数传递函数的参数传递用函数调用所给出的实参(实际参数,Actual Argument)向函数定义给出的形参(形式参数,Formal Argument)设置初始值的过程称为参数传递(Argument Passing)。函数调用时,形参被分配空间,并以相应的实参对形参进行初始化(此过程称为虚实结合或参数传递)。(注意:形参是一个局部于该函数的局部变量,在程序线程执行到此函数调用时,形参才被分配空间。)实参向形参传递的语义与变量/常量初始化的语义相同。在
8、虚实结合过程中,编译器将对实参和形参在个数、次序及类型上做相对应的匹配检查,并进行必要的隐式类型转换。例如:第6章 函 数第6章 函 数根据形参类型的类别,参数传递可分为以下两类:值调用(Call by Value)和引用调用(Call by Reference)。所谓值调用,即实参向形参传递的是实参的值;而对于引用调用,实参向形参传递的是实参的引用。在C+中,除了定义成引用类型的形参外,其它类型的形参都对应着值调用。在函数内对值调用的形参的值所进行的修改,不会影响调用方的实参变量;函数中对引用调用的形参的值所进行的修改,会影响调用方的实参变量。例如:第6章 函 数第6章 函 数第6章 函 数
9、当形参的类型为数组类型,虚实结合时,数组的首地址将被传递给形参,即此时参数的类型将从T类型转换为T*类型。例如:int strlen(const char*p);/C标准库函数strlen的原型声明void sort(int a,int length);/用户自定义的排序函数原型声明void f()char v=“an array”;int i=strlen(v);/虚实结合等价于const char*p=(char*)&v0;int j=strlen(“Nicholas”);/*虚实结合等价于第6章 函 数 const char*temp=“Nicholas”;const char*p=(c
10、har*)&temp0;*/sort(v,strlen(v);/等价于a=v,数组的整体赋初值若在调用函数时,不允许在函数体内修改形参的值,如上例所示,应将形参以const约束。第6章 函 数6.3 函数的返回值函数的返回值当一个函数声明其返回类型为非void时,函数应返回一个值;相反,当返回类型声明为void类型时,函数应不返回值。函数的返回值由函数体中的return语句实现。在一个函数体内可有多个return语句,但每一次函数调用应保证只有一个return语句被执行。利用“return;”语句亦可实现函数不返回值的功能。第6章 函 数函数返回的语义和参数传递的语义类似,它等同于以retur
11、n语句中的表达式向一个未命名的(匿名的)、一个函数返回类型的变量赋初值。在函数返回时会检查返回语句表达式的类型与函数的返回类型是否匹配,并自动进行两者必要的类型转换。正如前面曾提到的那样:形参和函数内定义的变量均属于局部变量或自动(Automatic)变量。它们的作用域为定义于它的函数,变量的生命期(局部的static变量除外)为此函数的一次调用。因此,一个指向局部变量的指针或引用永远亦不应该从函数中返回,尽管C+的语法对此并无限制。例如:第6章 函 数第6章 函 数上述代码中的两个return语句虽无语法错误,但语义是错误的。由于local是一个局部变量,当函数体执行完时,系统释放其所分配的
12、内存空间,因此,将无法返回其指针或引用。正确的做法是:将local定义为一个static类型的局部变量。由于一个局部的static类型的变量其生命期为程序的一次运行,作用域为定义于它的块或函数,故当函数体执行完时,它仍然存在,所以可返回其地址或引用。在一个返回值类型为 void的函数中,可无return语句,或出现没有返回值的return语句,或出现调用另一个返回值类型为void函数的return语句。例如:第6章 函 数第6章 函 数6.4 函数名的过载函数名的过载/重载重载6.4.1 函数名过载函数名过载/重载的基本概念重载的基本概念通常,我们应该为不同的函数起不同的函数名,以明确表示它们
13、完成的功能。但当某些函数概念上是完成同样的任务(即具有相同的功能),只是其操作对象(指形参)的类型不同时,那么,为这些函数起一个共同的名字可能更方便于使用。用同一个名字表示对不同类型对象的操作被称为(名字)重载/过载(Overloading)。实际上,名字重载的概念我们早已接触过。例如,符号(Notation)“+”根据其所处的上下文,既可以表示两个整型量加,亦可以表示两个浮点型量的加或其它类型量的加,等等。第6章 函 数一组具有重载/过载的函数名(Overloaded Function Names)的函数(简称过载/重载函数),是指它们在同一区域内有相同的函数名、不同的参数表(参数数目不同,
14、或类型不同,或顺序不同)。但是,仅仅是返回值类型不同的两个过载/重载函数,编译程序不按过载/重载函数对待,而按重复定义了两个函数进行报错处理。例如,我们可定义如下过载函数:int add(int x,int y);float add(float x,float y);double add(double x,double y);第6章 函 数6.4.2 重载函数的匹配规则重载函数的匹配规则如果程序中出现了一组重载的函数,则编译器在进行静态绑定时,根据重载函数参数表的内容进行唯一的匹配绑定(即静态确定函数调用与函数定义代码的匹配)。重载函数静态绑定时,按以下原则进行:(1)准确匹配,即形参和实参个
15、数、次序相同,类型无须任何转换或者只需做平凡转换(例如数组到char*、函数名到函数指针、T到const T等)的匹配。(2)提升的匹配,即形参和实参个数、次序相同,但类型需要提升转换(如bool到int、short到int以及short到unsigned int、float到double等)。第6章 函 数(3)利用标准转换的匹配,例如int到double、double到int、double到long double、子类到父类(详见第二部分的内容)、T*到void*、int到unsigned int等。(4)用户自定义的类型转换,详见第二部分的内容。(5)函数声明中的省略号(.)的匹配。例如
16、对一组重载的函数:void print(int);void print(const char*);void print(double);void print(long);void print(char);第6章 函 数void h(char c,int i,short s,float f)print(c);/准确匹配,调用print(char)print(i);/准确匹配,调用print(int)print(s);/提升匹配,调用print(int)print(f);/提升匹配,调用print(double)print(a);/准确匹配,调用print(char)print(“student”);/准确匹配,调用print(const char*)第6章 函 数在函数调用的匹配时,若出现了两个或两个以上可能的匹配,则该函数调用被认为存在歧义性而遭拒绝,此时编译器报错。例如:void print(float);void print(double);void print(long);void f()print(1L);/正确!匹配print(long)print(1.0);/正确!匹配pr
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1