第2章函数第2章函数2.1函数的定义与使用2.2函数调用机制2.3递归函数2.4默认参数的函数2.5内联函数2.6函数重载2.7函数模板2.8使用C++系统函数第2章函数2.1.1函数的定义一个完整的函数定义由两部分组成,即函数头与函数体。1.函数定义的一般语法形式类型标识符函数名说明符(形式参数表)//函数头{//函数体说明性语句序列;实现函数功能的语句系列;}2.1函数的定义与使用2.函数的类型和返回值类型标识符规定了函数的返回值类型,它返回给主调函数的处理结果,由函数体部分的return语句带回。无返回值的函数其类型标识符为void,不必有return语句。第2章函数3.形式参数函数头部分的形式参数(简称形参)表的内容如下:类型l形参名1,类型2形参名2,...,类型n形参名n其中类型1、类型2、...、类型n是类型标识符,表示形参的数据类型(int、double、float、char、bool等);形参名1、形参名2、...、形参名n是形参名(合法的自定义标识符)。形参是用来实现主调函数与被调函数之间的数据联系,通常将函数所处理的数据、影响函数功能的因素或者函数处理的结果作为形参。对于无形参的函数,其形参表的内容应该为空,但代表函数的小括号对不能省略。第2章函数2.1.2函数的调用函数遵循先声明后调用的原则,如果没有遵守先定义后调用的原则,调用函数之前先要在主调函数中声明函数原型。在主调函数中,或所有函数之前,按如下形式进行函数原型声明:类型标识符被调函数名(含类型说明的形参表);注:如果是在所有函数之前声明了函数原型,那么该函数原型在本程序文件中任何地方都有效;如果是在某个主调函数内部声明了被调函数原型,那么该原型就只能在这个函数内部有效。声明了函数原型之后,便可以按如下形式调用子函数:函数名(实参1,实参2,…,实参n)实参列表中应给出与函数原型中形参个数相同、类型相符的实参,每个实参都可以是常量、变量或表达式三者之一。第2章函数2.1.3函数的参数传递函数的参数用于在调用函数与被调用函数之间进行数据传递。在函数定义时,函数名后面括号内的参数称为形式参数(简称形参)。在函数被调用时,函数名后面括号内的参数称为实际参数(简称实参)。当函数未被调用时,C++编译系统并没有给函数的形参分配相应的内存空间,函数的形参更不会有实际的值。只有在函数被调用时,C++编译系统这时才为形参分配实际的存储单元。函数的参数传递,指的就是形参与实参结合(简称形实结合)的过程。形实结合的方式有值调用和引用调用两种。第2章函数1.值调用值调用是指当发生函数调用时,编译系统为形参分配相应的存储空间并且直接将实参的值复制给形参,这样形参和实参就各自拥有不同的存储单元,且形参是实参的副本。因此,值调用过程是参数值的单向传递过程,一旦形参获得了与实参相同的值就与实参脱离关系,以后不论形参发生多大的改变,都决不会反过来影响到实参。传值方式可以防止被调用函数改变参数的原始值,这在很多场合是很重要的。第2章函数#includeiostream.hvoidswap(inta,intb);voidmain(){intx,y;x=5;y=10;coutx=xy=yendl;swap(x,y);//交换x,y的值coutafterswapendl;coutx=xy=yendl;}【例2-1】从键盘输入两个整数,交换位置后输出。voidswap(inta,intb){intt;t=a;a=b;b=t;}第2章函数程序运行结果为x=5y=10afterswapx=5y=10分析:程序运行结果可以看出,并没有达到交换的目的。因为在单向值传递方式中,形参值虽确实进行了交换,但这些改变对实参不起任何作用。函数调用语句swap(x、y)后,编译系统将实参x中的值5传递给虚参a,将实参y中的值10传递给虚参b;在swap函数中,a、b中的值完成互换;返回主函数时,实参x、y中的值不受虚参a、b的影响,并未进行交换。第2章函数2.引用调用显而易见,值调用时参数的传递方式是实参单向复制其值给虚参,如果想使子函数中对形参所做的任何更改也能及时反映给主函数中的实参(即希望形参与实参的影响是互相的或称是双向的),就需要改变调用方式,即采用第二种参数传递方式——引用调用。引用是一种特殊类型的变量,可以被认为是某一个变量的别名。通过引用名与通过被引用的变量名访问变量的效果是一样的。这就是说,对形参的任何操作也就直接作用于实参。第2章函数例如:inta,b;int&ra=a;//建立一个int型的引用ra,初始化为变量a的一个别名b=10;ri=b;//相当于a=b;注意:①声明一个引用时,必须同时对它进行初始化,使它与一个已存在的对象关联。②一旦一个引用被初始化后,就不能改变关联对象。换言之,一个引用从它被声明之后,就必须确定是哪个变量的别名,而且自始至终只能作为这一个变量的别名,不能另作他用。形参也可以引用的方式出现在形参表中形参的初始化不在类型说明时进行,而是在执行主调函数中的调用语句时,才为形参分配内存空间,同时用实参来初始化形参。第2章函数#includeiostream.hvoidswap(int&a,int&b);voidmain(){intx,y;x=5;y=10;coutx=xy=yendl;swap(x,y);//交换x,y的值coutafterswapendl;coutx=xy=yendl;}【例2-2】使用引用调用改写例2-1的程序voidswap(int&a,int&b){inttemp;temp=a;a=b;b=temp;}程序运行结果为:x=5y=10afterswapx=10y=5分析:参数被调用时,它们分别被初始化成为a和b的别名。在子函数swap中将两个参数的值进行交换后,交换结果可以返回主函数main第2章函数2.2函数调用机制一个C++的源程序经过编译以后形成与源程序主名相同但后缀为.exe的可执行文件,且存放在外存储器中。当该.exe的可执行程序被运行时,首先从外存将程序代码装载到内存的代码区,然后从main函数的起始处开始执行。程序在执行过程中,如果遇到了对其它函数的调用,则暂停当前函数的执行,保存下一条指令的地址(即返回地址,作为从子函数返回后继续执行的入口点),并保存现场(主要是一些寄存器的内容),然后转到子函数的入口地址,执行子函数。当遇到return语句或者子函数结束时,则恢复先前保存的现场,并从先前保存的返回地址开始继续执行。图2-1说明了函数调用和返回的过程,图中标号标明了执行顺序。第2章函数图2-1函数调用和返回的示意图第2章函数2.3递归函数其特点是,在函数内部直接或间接地自己调用自己。所谓直接调用自身,就是指在一个函数的函数体中出现了对自身的调用语句。例如:voidfunc1(void){...func1();//func1调用func1自身...}第2章函数所谓间接调用自身,就是一个函数func1调用另一个函数func2,而函数func2中又调用了函数func1,于是构成间接递归。下面的例子属于间接调用情况。voidfunc1(void){...func2();//func1调用func2...}voidfunc2(void){...func1();//func2调用func1...}func1函数就是通过func2实现间接递归。第2章函数【例2-3】有5个人坐在一起,问第1个人多少岁,他说比第2个人大2岁。问第2个人多少岁,他说比第3个人大2岁。问第3个人多少岁,他说比第4个人大2岁。问第4个人多少岁,他说比第5个人大2岁。最后问第5个人,他说是12岁。请问第1个人多少岁?分析:这是一个递归问题。每一个人的年龄都比其后那个人的年龄大2,即可以用公式表示如下:12(n=5)age(n)=age(n+1)+2(n5)可写一递归函数表示:intage(intn){intss;if(n==5)ss=12;elsess=age(n+1)+2;return(ss);}第2章函数2.4默认参数的函数在函数定义中通过赋值运算就可指定默认参数值。一旦程序在调用该函数时,如果给出实参,则用实参初始化形参;如果没有给出实参,则C++编译系统自动以预先赋值的默认参数值作为传入数值。一般情况下都将调用该函数时经常用到的常数作为默认参数值,这样在调用时就无需每次都写出该值了。指定默认参数值可以使函数的使用更为简单,同时也增强了函数的可重用性。第2章函数#includeiostream.hintmult(intn,intk=2){//第二个形参具有默认值if(k==2)return(n*n);elsereturn(mult(n,k-1)*n);}【例2-4】带默认形参值的函数例题。main(){coutmult(5)endl;/*形参n用实参来初始化为5,形参k采用默认值2,实现5*5*/coutmult(5,3)endl;/*用实参来初始化形参,n为5,k为3,实现5*5*5*/}程序运行结果为25125第2章函数注:默认形参值必须按从右向左的顺序定义。在有默认值的形参右面,不能出现无默认值的形参。因为在调用时,实参初始化形参是按从左向右的顺序。例如:voidtry(intj=3,intk)//非法voidtry(intj,intk=2,intm)//非法voidtry(intj,intk=7)//合法voidtry(intj,intk=2,intm=3)//合法voidtry(intj=3,intk=2,intm=3)//合法第2章函数在相同的作用域内,默认形参值的说明应保持唯一。但如果在不同的作用域内,允许说明不同的默认形参。这里的作用域是指直接包含着函数原型说明的大括号所界定的范围。对作用域概念的详细介绍请参阅第5章。例如:intadd(intx=2,inty=5);//全局默认形参值voidfunc(void){add()//采用全局默认形参值,x取值2,y取值5}voidmain(){intadd(intx=1,inty=9);//局部默认形参值add();//采用局部默认形参值,x取值1,y取值9}第2章函数2.5内联函数内联函数(也称在线函数)是在C++中为提高程序运行效率而引入的。所有函数调用时都会产生一些额外的开销,主要是系统栈的保护、代码的传递、系统栈的恢复以及参数传递等。对于一些函数体很小但又经常使用的函数,由于被调用的频率非常高,这种额外开销也就很可观,有时甚至会对运行效率产生本质的影响。使用内联函数正是解决这一问题的手段。第2章函数内联函数不是在调用时发生转移,而是在编译时将函数体嵌入在每一个调用语句处。这样就相对节省了参数传递、系统栈的保护与恢复等的开销。内联函数在定义时使用关键字inline区别于一般函数,其语法形式如下:inline类型标识符被调函数名(含类型说明的形参表){函数体}第2章函数例如:inlineintmul(inta,intb){returna*b;}当程序中出现mul(2+3,4)的函数调用时,编译程序就会将其扩展为(2+3)*4。关键字inline是一个编译命令,编译程序在遇到这个命令时将记录下来,在处理内联函数的调用时,编译程序就试图产生扩展码。这样从使用者的角度来看,内联函数在语法上与一般函数没有什么区别,只是在编译程序生成目标代码时才区别处理。第2章函数注意:①内联函数体内一般不能有循环语句和switch语句。②内联函数的定义必须出现在第一次被调用之前。③对内联函数不能进行异常接口声明。如果违背了上述注意事项中的任一项,编译程序就会无视关键字inline的存在,像处理一般函数一样处理,不生成扩展代码。因此,只有很简单而使用频率很高的函数才被说明为内联函数。内联函数会扩大目标代码,使用时要谨慎。第2章函数2.6函数重载函数的重载