370 likes | 516 Views
第 8 章 函 数. 一、函数与调用约定 二、函数的总体概念. 一、函数与调用约定 将经常使用的功能组合成一个有机的整体,程序就划分 为功能相对独立的模块。这些独立的模块对应着程序的函 数。 函数是代码最重要的重用机制。函数的来源分为两种: 一、 程序员编写的函数; 二、 系统提供的标准库函数。 系统的库函数是预先编好的可供程序员调用的函数。调 用时需要将库函数的原型通过头文件的方式包含在主控函数 的源文件中。 例如: 通过 #include<math.h> ,就可以使用 math.h 中的数学
E N D
第8章 函 数 • 一、函数与调用约定 • 二、函数的总体概念
一、函数与调用约定 • 将经常使用的功能组合成一个有机的整体,程序就划分 • 为功能相对独立的模块。这些独立的模块对应着程序的函 • 数。 • 函数是代码最重要的重用机制。函数的来源分为两种: 一、程序员编写的函数; • 二、系统提供的标准库函数。 • 系统的库函数是预先编好的可供程序员调用的函数。调 • 用时需要将库函数的原型通过头文件的方式包含在主控函数 • 的源文件中。 • 例如: • 通过#include<math.h>,就可以使用math.h中的数学 • 函数如sin,cos等。
调用约定是为实现函数调用而建立的一种协议。函数定调用约定是为实现函数调用而建立的一种协议。函数定 • 义之后,可以在别的地方对它进行调用。在定义时用形式参 • 数(简称形参),调用时则替换成实际参数(简称实参)。 • 首先,参数的传递是指入口形参输入传递,它是一个 • 形实替换过程。 • 如果一个程序向另一个程序发送参效的规则和后者接收 • 参数的规则不符合,那么程序就可能因为出错而导致系统崩 • 溃。 • 其次,参数的传递也包括函数返回的数据传递。
在各种高级语言中比较流行的参数的传递方式主要为如在各种高级语言中比较流行的参数的传递方式主要为如 • 下两种: • 1.传值调用 (call by value) • 传值是一种最简单的参数传递方法,传值首先指输入传 • 值,它把实参的值单方向地传递给相应的形参。对于变量、 • 指针等实参,被调用段无法改变实参的值,传值是一种最安 • 全的参数传递方法。 • 传值包括形参的输入传值与数据的返回传值。例如: • int ifi (int x) {return x+1;} • //int型的数值入口和int型的数值返回 • int* pfp (int* p) {return p+2;} • // int*型的数值入口和int*型的指针数值返回
输入传值过程把实参值存放在一个被调用段可以取得的输入传值过程把实参值存放在一个被调用段可以取得的 • 地方即形参中,每一个入口形参位于被调函数新开辟的堆栈 • 空间而非原来的变量中。 • 进入被调用段后,首先在临时的堆栈空间取出实参值, • 然后象对待一个局部变量一样对形参进行处理。 • 在被调函数中,一切操作都针对此局部的独立的形参单 • 元进行。如果实参不是变量的地址,则被调用段是无法改变 • 实参对应的变量值的,即对原实参对应的变量无影响。 • 实参为指针的情况,传值也是传递指针所具有的数值不 • 是指针的地址,即传递另一个变量的地址给被调程序,作为 • 被调程序指针形参的初始值,在被调用段可以改变指针指向 • 的存储单元的值,不能改变原实参对应的指针的值。传值调 • 用传递一个不含实参地址属性信息的右值。
传值调用的特点是先计算出表达式的值,输入的时候对传值调用的特点是先计算出表达式的值,输入的时候对 • 于大的对象或结构变量须把其一系列具体的数据值放置在刚 • 开辟的堆栈空间中,堆栈空间的开销趋大。 • 传值返回的时候根据返回数据类型的大小系统将结果值 • 或通过EAX等寄存器或通过临时建立的存储单元返回给主控 • 程序。 • C语言函数的参数传递方式只有一种就是数值传递。
2.引用调用(call by reference) • call by reference视为变量的传址或称为变量的引用 • 传递。指针的传值与变量的引用传递都是转送另一个变量的 • 地址但对应两种不同的调用约定。 • 引用调用包括变量和指针以及函数指针等的引用传递, • 包括引用输入与引用返回。例如: • int& rif (int& x) {return x+=1;} • //int&变量的引用入口和引用返回 • int*& rpf (int*& p) {return p+=2;} • //int*&指针的引用入口和引用返回
引用输入传递也是用得最多的参数传递方式,它是把实引用输入传递也是用得最多的参数传递方式,它是把实 • 际参数的地址传递给相应的形式参数。引用调用传递一个含 • 实参地址属性信息的左值。其实现过程下: • 在被调函数中,每一个形参都对应了一个形参单元,这 • 个单元用来存放相应实参的地址。 • 如果实参是变量则直接传递它的地址;如果实参是常数 • 或表达式,则应该首先计算出它的值并放入一个临时单元, • 再把这个临时单元的地址传给函数。 • 当进入被调用函数后,对应的形参单元中存放的是实参 • 的固定地址,在处理数据时,针对这些形参指向的实参地址 • 进行访问,但程序段中采用的是变量名的语法。在被调函数 • 返回时,这些形参单元所对应的实际单元就直接得到结果值.
如果实参的类型不同于形参的类型,不同版本编译器做如果实参的类型不同于形参的类型,不同版本编译器做 • 出的反应是不同的: • 或者建立临时变量,将临时变量的地址作为下一步计算 • 的依据; • 或者禁止引用形参和实参的类型不一致(vc6.0属此情 • 况)。引用形参关键之点是被调函数直接操作实参代表的内 • 存空间。 • 引用调用的核心在于: • 变量具有双重属性,变量的地址属性和变量的值属性, • 被调函数拥有实参的双重属性; • 传址就是通过变量的地址属性,快捷地存取变量的值, • 在函数体中,间接寻址的地址未变,而其地址对应的内容可 • 发生变化。
引用调用在变量输入的时候不论是小的字符变量或是大引用调用在变量输入的时候不论是小的字符变量或是大 • 的对象只将实参变量的地址压入堆栈。 • 传址返回的时候也直接返回相关变量的地址属性,不需 • 要额外建立临时对象或临时变量。 • C++作为C的超集,继承了C语言根深蒂固的参数传值 • 调用方式,又借鉴FORTRAN 等高级语言中高效的引用调 • 用,形成两种调用并存的局面。 • C++语言是典型的混合编程语言。
二、函数的总体概念 • C/C++中函数是由称之为函数体的可整体运行的若干语 • 句构成,这些语句对表征数据状态的名称进行预定的运算操 • 作。 • 编译器根据函数名、形参类型、形参个数与形参位置来 • 鉴别函数体代码段入口地址的唯一性,函数调用作为表达式 • 可返回某种类型的数据。 • 类名抽象type,T1, T2, T等可以是int, char,long,float • 等,也可是结构名、或类类型名等。
1.函数的返回类型 • 函数的返回类型由函数名前的类型标识符指定。不失一般性以两个形参进行说明。根据函数的调用机制与返回结果可以分为两大种类: • a.函数操作结果没有返回值。例如 • void vf (T1,T2*){ return ;} • [例] void vf (int a,int* p) { a+=3; p++;} • 用关键字void前置加以声明的函数称为void型函数。 • void型函数只独立调用,一般不参入表达式的嵌套运算,除 • 非出现在三目操作数表达式的后两个操作数中以及逗号运算 • 符分隔的操作数中。
b.函数操作的结果具有确定的返回值,这样的函数调b.函数操作的结果具有确定的返回值,这样的函数调 • 用是表达式,可参入表达式的嵌套运算,也可单独调用。 • 主要可分为两种形式: • (1)返回一个数值类型的数据。例如: • type funct (T1 v, T2* p) ; • type* pfunct (T1 x , T2* q) ; • [例] • long min(short s,long* p) {return s<*p?s:*p; } • long* pan(int n , long* p){ return p+n;};
这种形式的函数操作的结果常见的有两种: • (一)、type型的传值返回; • (二)、指针的传值; • 返回即函数返回的结果为type*型的指针值。指针的值 • 最终必然用于操作内存的数据,因此该指针监控的内存空间 • 的生存期对于主控程序应是有效地可访问的。 • 返回算术或指针类型数值的函数调用为右值表达式。 • 例如: • 函数调用min (s,p)是long型的右值,pan(n,p)是long* • 型的右值地址,但间接访问*pan (n,p)是long型的左值。
(2)返回一个引用类型的数据。例如: • type& rf (T1&, T2& ) ; • type*& rf (T1&, T2* &); • [例] long& rmax (long& n,long& m) { return n>m?n:m; } • long*& rpan (int& n,long*& p) { return p+=n; } • 返回type&类型的函数为返回变量的引用的函数。返回 • type*&类型的函数为返回指针的引用的函数。返回引用的函 • 数调用为左值表达式。 • 例如:函数调用rmax(n,m)是long型的左值变量,函数 • 调用rpan ( n, p)是long*型的左值指针。引用概念是C++所 • 独有的。 • 函数不能返回函数和数组,但可以返回指向数组或函数的 • 指针。
2.函数的定义 • a.函数数值传递的定义 • type funct (T1 x, T2* y) /*标题头*/ • { /*x是变量的数值形参,y是指针数值形参*/ • 语句序列; • return expre; /*expre是可转换为type型的表达式*/ • } /*最外的一对花括号界定函数体*/ • 例: • long min (long x,short* y) • { • if (x<*y) return x; • else return *y; • }
b.函数引用传递的定义 • type & rf (T & v, T* & p) • { /*v是变量的引用形参,p是指针引用形参*/ • 语句序列; • return Lvalue; /*Lvalue 为type型的左值*/ • } • 例: • int& rmin (int& v, int*& p) • { • if (v<*p) return v; • else return *p; • }
由于传值的输入不改变相关实参变量的值,如果要保持由于传值的输入不改变相关实参变量的值,如果要保持 • 原先函数的这一特性,在引用形参前进一步加上修饰词 • const,得到: • const T& cf(const T& v, T*& p) • { 语句序列; • return (T型的变量); • } • 例: • const int& cmin (const int& v, int*& p) • { if (v<*p) return v; • else return *p; • }
形参表中的const引用形参v是局限于函数体的不变量,形参表中的const引用形参v是局限于函数体的不变量, • 这种出现在形参位置的不变量虚实结合时可以获得不同的实 • 参变量,在函数体中只作为右值。 • 函数的返回类型前加const关键字界定,此时限制返回 • 引用的函数调用不作为左值但保留数据传递的效率。 • 函数名代表了函数的入口地址。圆括号中()可以无任何 • 参数,称为无参函数,此时圆括号中可放置关键字void。在 • 这种情况下对函数的调用称为无参调用。 • 圆括号()包括的参数称为形式参数,代表函数与调用 • 段的数据接口。T1,T2, T,type 是类型名,声明数据的类型, • v,p, x,y是形参名。
函数名、类型名和形参名遵循标识符的命名规定。参数函数名、类型名和形参名遵循标识符的命名规定。参数 • 之间用逗号分隔,函数名前的类型就是函数的返回类型。 • 函数不能嵌套定义即在函数体中定义另外一个函数实 • 现。 • 函数定义意味形参未曾进行实参化,函数调用则对应虚 • 实结合过程。 • 函数体内无论多少条语句,花括号是不可省的。用花括 • 号括起来的语句序列组成了函数体。
语句序列可以是0条、1条或多条语句。 • 当函数体是0条语句时,称该函数为空函数。空函数作 • 为一种什么都不执行的函数也是有意义的。 • 例如:下面的NoOperate()就是一个空函数。 • void NoOperate (void) { } • 函数可以略去形参名仅带类型参数,通常表示预定的接口 • 准备。例如: • void PreOperate (long/*cx*/,float/*dx*/) { }
3.函数原型 • 函数的类型首先指函数执行后返回值的类型,其次包括 • 函数的入口类型,两者一起确定函数的类型属性。函数原型 • (function prototype)反映函数的类型属性,函数原型称为 • 函数说明。C++中所有的名称在索引点之前必须有效说明。 • 函数定义在先调用在后,调用前可以不必说明;函数定 • 义在后,调用在先,调用前必须说明。一般将函数原型的说 • 明放在程序全局范围的开始部分。 • 函数原型表明入口参数的类型、位置、个数和返回类 • 型。函数原型的说明方法如下: • type funct (T1 v, T2* p); • 类型 函数名(类型1 形参1,类型2* 形参2);
函数原型中的形参名可以和标题头相应的形参名不同。函数原型中的形参名可以和标题头相应的形参名不同。 • 例如: • long& rmax (long& n,long& m); • long& rmax (long& x,long& y); • 原型中形参名是可有可无的,但函数原型中形参的位 • 置、类型、个数与函数名一起构成函数原型的关键因素。写 • 成略去形参名的形式: • long& rmax (long&,long&); • 或一般地: • type funct (T1 , T2* ); • 类型 函数名 (类型1,类型2* );
从函数原型可知funct函数第一个形参的数据类型为从函数原型可知funct函数第一个形参的数据类型为 • T1,第二个形参为T2*型的指针数值形参,返回type型的数 • 据。 • 函数原型与函数定义的标题头十分类似:将函数定义的 • 标题头进行复制就得到函数原型的说明,但记住在末尾加一 • 个分号";"。 • 编译器根据函数原型或标题头来识别不同的函数实现, • 函数实现部分中的语句序列对于函数的区别并不重要。
下面是两个重要函数printf和scanf的原型,原型是从系下面是两个重要函数printf和scanf的原型,原型是从系 • 统stdio.h头文件拷贝过来的,进行了适当简化。三点省略 • 号...表示可变参量列表,表示实参的个数是动态可变动的。 • int printf (const char *format, ...); • int scanf (const char *, ...); • const char *表示只读指针形参,实参常见的是格式控 • 制字符串常数。例如: • printf ("abcd"); printf ("%d,%d",x,y); • scanf("%d,%d",&x,&y); • 可以改写为: const char * r="abcd"; • const char *fmt="%d,%d"; • printf (r); printf (fmt,x,y); scanf (fmt,&x,&y);
4.return 语句 • 函数的返回通过return语句实现。return语句有三种格 • 式,如下所示: • 格式一: return; • 格式二: return expre; return (表达式); • 格式三: return Lvalue; return 左值表达式; • return语句后面的圆括号是可选的,return (expre);等 • 价于return expre;
关于return语句的使用说明如下: • 格式一用于void型无返回值的情况。无返回值的函数须 • 用void来说明。该函数中可以有return语句,也可以无 • return语句。 • 当一个被调用函数中无return语句时,程序执行到函数 • 体的最后一条语句时,返回到调用函数,相当于函数体的花 • 括号有返回的功能。 • 函数中也可以有多个return,它们大多出现在if语句 • 中。当使用无表达式的返回语句时,返回程序执行的控制 • 权。
格式二是用于数值返回的调用函数中,这也是传统的C格式二是用于数值返回的调用函数中,这也是传统的C • 语言中的返回方式,return之后表达式的值就是函数调用的 • 值。最简单的表达式是变量或常数。 • 格式三用于引用返回的函数调用中,这是C++新增的函 • 数返回约定。这种约定对应着对引用或相关变量的直接操 • 作,因此当函数引用返回时返回语句return后不要跟右值表 • 达式,而只返回左值表达式。表达式expre或Lvalue可以是 • 返回值的函数调用。
具有return 语句实现过程如下: • a.先计算出表达式expre或Lvalue的值,表达式完成所 • 有的副作用。 • b.算术表达式expre的类型与函数的类型不相同时,将 • 表达式的类型自动转换为函数的返回类型,这种转换是强制 • 性的,可以出现不保值的情况。 • 返回引用时的左值一般应与返回类型一致。指针表达式 • 也应与函数的返回类型一致。 • c.将程序执行的控制权由被调函数转向主控函数, 执 • 行主控函数后面的语句。
void func1() { func2(); 语句; ::: return; } int func2() { 语句; ::: return 1; } void main(void) { 语句; func1(); 语句1; ... funck(); 语句k; ... funcn(); 语句n; } • 图 函数调用和返回过程 void funck() { func(); 语句; ::: return; } char func() { 语句; ::: return '1'; }
函数体中的变量为局部变量,非静态的局部变量存放在函数体中的变量为局部变量,非静态的局部变量存放在 • 堆栈空间。 • 堆栈空间是动态变化的,函数是分层分层调用的。主控 • 函数与被调函数各自的局部变量都位于堆栈空间中,被调函 • 数的局部变量位于堆栈空间变化快的部分,主控函数的局部 • 变量位于堆栈空间变化慢的部分。不要返回变化快的局部变 • 量的堆栈地址给上层的主控函数。 • 即上面的 Lvalue 应是形参表中的引用或全局变量或全 • 局数组元素或静态变量。
5.函数的使用 • 函数定义之后就可以使用,使用函数也称为函数调用。 • 函数名加上圆括号包括的不带类名的实参序列构成函数 • 调用。 • 函数调用分三个主要步骤: • a.虚实结合 • b.执行函数体中的代码序列 • c.函数返回 • 函数调用对应形参初始化。实参对形参初始化是按其位 • 置对应进行,即第一个实参的值赋给第一个形参,第二个实 • 参的值赋给第二个形参,依此类推。
实参表达式在虚实结合之前已进行求值计算,编译器习实参表达式在虚实结合之前已进行求值计算,编译器习 • 惯上从右到左的次序对实参表达式进行求值计算。 • 但可以从左边对应的实参开始求值一直往右,这种实参 • 求值次序的不同可能引起函数调用的一些副作用,因此编程 • 时保证实参表达式的值独立于编译器左右求值的不确定性。 • 对于算术型数值形参,如果实参类型不同于形参类型则 • 进行必要的类型转换,转换的方向从实参的类型到形参的类 • 型。指针形参和引用形参要求严格匹配相应的实参类型。 • 数值形参重要的性质是其安全地承前启后的作用,在堆 • 栈获得初始值之后,数值形参本身在函数体中的变化不影响 • 主控函数,并且它们占有的独立的内存可以充分加以利用。
引用形参对应的实参是变量或左值表达式,执行传址调引用形参对应的实参是变量或左值表达式,执行传址调 • 用之前系统得到的是实参变量的地址,传递给形参的是左值 • 实参,隐含地通过左值实参的地址间接操作实参的数值。 • 函数单独调用构成函数调用语句,有值返回的函数单独 • 调用时,系统会建立一个临时变量以保存函数的返回值。 • 函数调用的一般格式为: • 函数指针表达式(实参表达式列表) • 函数名是最简单的函数指针表达式。
函数调用根据函数的返回类型来确定: • (1).无值返回的函数单独调用。如: • vf (v1,v2); swap (&a,&b); swap (a,b); • //函数调用语句 • (2).返回右值的函数可作为右值表达式参入各种运算。 • 如: • var=20* funct (v1,v2); int x=min (n,p); • long* q=pan (n,p); • (3).返回左值即返回引用的函数可作为左值表达式参入 • 各种运算。如: • rf (v1,v2)+=1; rmax (x,y)-=2; rpan (n,p)--;
返回值的函数调用作为表达式其结果就是return中转送返回值的函数调用作为表达式其结果就是return中转送 • 过来的表达式,这称为函数对于相应表达式的映射。 • 返回引用的函数Lvalue可以出现的地方函数调用 • rf(v1,v2)作为左值可以等价地出现,返回算术或指针数值的 • 函数funct将expre映射成右值表达式。 • 在这种映射之前函数可以执行许多有意义的运算。 • 返回值的函数既具有动作本身又携带数据信息,这一性 • 质表明函数是算法和数据封装的基石。