Download presentation
Presentation is loading. Please wait.
1
第4讲 函数的高级应用 主讲教师:资讯系 张玉宏
2
1.函数重载( function Overloading)
C++能提供函数重载的原因,是因为它是一种对数据类型(data type)的检查和控制非常严格的语言。对于编译器而言,只要参数不相同,不管是数目不一样还是数据类型不同,都视为不同的函数。 升达大学资讯系
3
1.函数重载( 续) 例如: int Max(int,int); 以及: 在程序中都可以因为调用函数所使用的参数不同而调用正确的函数。
float Max(float,float);//二者参数类型不同 以及: double Area(double x); double Area(double x,double y);//参数的数目不同 在程序中都可以因为调用函数所使用的参数不同而调用正确的函数。 升达大学资讯系
4
重载的例程 运 行 结 果 升达大学资讯系
5
函数的签名 C++要求重载的函数具有不同的签名(Signature)。函数的签名包括: 函数名 函数的参数个数、类型、顺序。
为了保证函数的唯一性,函数必须拥有独一无二的签名。 两个函数: int M(double,int); int M(int, double); 具有不同的函数签名,因为它们的参数的次序不同。 升达大学资讯系
6
函数的签名(续) 函数的数据返回类型不是函数的签名的一部分,所以重载函数不同通过返回值类型来加以区分。 例如: int a(int);
double a(int); 不能相互区分,编译器将产生一条出错信息: ERROR:not distinct from s above 升达大学资讯系
7
2.函数默认参数 C++允许程序员以常数形式为函数参数设定默认值。如果在调用函数时不提供这个参数,则将使用这个默认值代替。例如,有一个函数Area(),它的第二个参数默认为12.0: 函数的声明为: float Area(float Width,float Length=12.0); 函数的定义为: float Area(float Width,float Length) {return Width*Length;} 如果调用时候没有写出第二个参数的值: A=Area(6.5);//只用一个参数调用 则会以默认值设定第二个参数,亦即A的值为:6.5*12.0 思考:为什么不可以认为Length的值是6.5,而Width还没有指定? 升达大学资讯系
8
2.函数默认参数(续) 默认值只能在函数的声明时指定或者是函数定义放在声明的位置的时候。但不可以同时在函数的声明式和定义式中指定。为了让编译器能正确区分,在参数列表中有默认值的参数必须放置在所有其他没有默认的参数的后边,不可以混杂。此外,在调用时,所有使用默认值的参数之后也必须使用默认值。例如,有一个函数的声明式为: void Func(float ,float x=0,int n=5,char =“r”) 则下面调用时正确的: Func(1.5); Func(3.2,1.0); Func(0.0,1.0,10,’a’); 而下面调用时错误的 Func(1.0,10,’a’);//类型不一致 也就是说函数调用的时候,实参和形参从左到右依次一一对应,若后面实参数量不够,则启用默认后面的默认函数参数。 升达大学资讯系
9
默认函数的例子 运行结果BC 运行结果VC #include <iomanip> using std::cout;
// --- 声明函数Area() float Area(float Width, float Length = 12.0); // --- 主程序 int main(){ float A; A = Area(6.5); // 只用一个参数调用 cout << "A is : " << A; return 0;} // --- 定义函数Area() float Area(float Width, float Length) { return Width*Length; } 运行结果BC 运行结果VC 升达大学资讯系
10
3.模板函数 3.1模板的引入 C++中加强了数据及函数类型的匹配检查,如果不慎写错一个变量名,编译器将帮助找出,从而加快错误定位和排除的速度。丰富的类型和严格的类型检查,大大方便了程序设计和排错。 但是丰富的类型和严格的类型检查,有时也会给我们带来一些麻烦。可能常常会遇到这样一种情况:对于很多数据类型,我们需要提供一种逻辑功能完全一样的函数,而编制这些函数提供的程序文本完全一样,其区别仅仅是处理的数据类型不同。 对于这种函数通常可以有两种实现方法,一种方法是使用宏函数,另一种方法是为各种类型都重载这一函数。但宏函数虽然方便,但是常常会引入一些意想不到的问题,因此C++中已经不提倡使用宏函数;而为各种数据类型重载有比较麻烦。模板函数就是适应这一要求而产生的。 升达大学资讯系
11
3.2模板函数的引入 模板函数(Function Template)是用来自动产生许多具有共同程序算法的函数的模板。这些函数将具有共同的逻辑和操作步骤,但其输入输出的数据类型可以不同。例如: template<class T> T Sum(T x,T y) {return x+y;} 相当于同时声明了很多名称都叫做Sum()的函数,但其输入输出类型未定,可以是int,float,double或char,真正的函数定义要由编译器判定实际使用的数据类型后才能完成。 上式中, template,class 都是C++的关键词。 template表示即将声明一个模板函数,而class 则用来声明以下模板函数所要使用的数据类型的暂定名称。理论上,任何名称都可以用来作为暂定的数据类型名称,但习惯都使用英文字母T。 注意:这里的模版数据类型class是指“用户定义的或固有的类型”,而字母T标识这个类型,这和即将讲到的类定义class是不相关的。虽然二者都是相同的关键词class 。 升达大学资讯系
12
3.2模板函数的引入(续) 模板函数(Function Template)是用来自动产生许多具有共同程序算法的函数的模板。这些函数将具有共同的逻辑和操作步骤,但其输入输出的数据类型可以不同。 例如: template<class T> T Sum(T x,T y) {return x+y;} 相当于同时声明了很多名称都叫做Sum()的函数,但其输入输出类型未定,可以是int,float,double或char,真正的函数定义要由编译器判定实际使用的数据类型后才能完成。 上式中, template,class 都是C++的关键词。 template表示即将声明一个模板函数,而class 则用来声明以下模板函数所要使用的数据类型的暂定名称。理论上,任何名称都可以用来作为暂定的数据类型名称,但习惯都使用英文字母T。 升达大学资讯系
13
模板函数的引入(续) 模板函数内可以列出多个参数,多个参数之间要用逗号隔开,而且每个参数都必须重复使用单词class,对所声明的参数数目没有限制。例如: template <class T1,class T2,class T3> T1 Func(T1 x,T2 y,T3 z) { //…函数内容 } 当然,这些参数名( T1,T2,T3)的作用域仅仅存在于该模板范围之内。建议最好使用描述性的参数名,并且使用以T开头的标识符以提醒我们注意它们的模板状态。如: template<class TData,class Tindex); 升达大学资讯系
14
模板函数的例程 模板函数内可以列出多个参数,多个参数之间要用逗号隔开,而且每个参数都必须重复使用单词class,对所声明的参数数目没有限制。例如: template <class T1,class T2,class T3> T1 Func(T1 x,T2 y,T3 z) { //…函数内容 } 当然,这些参数名( T1,T2,T3)的作用域仅仅存在于该模板范围之内。建议最好使用描述性的参数名,并且使用以T开头的标识符以提醒我们注意它们的模板状态。如: template<class TData,class Tindex); 升达大学资讯系
15
template <class T> T max(T x,T y){return (x>y)?(x):(y);}
//模板函数形同一群重载函数的定义。 #include <iostream.h> void main() { int x=3,y=4; double a=1.2 ,b=2.6; cout<<max(x,y)<<endl;//调用模板函数int(int,int) cout<<max(a,b)<<endl; //调用模板函数double(double, double) } 说明:函数模板里面必须包含完整的内容,只是数据类型暂时还不确定罢了。置放模板函数的位置和函数原形相同,都位于主程序之前 升达大学资讯系
16
4.函数与引用 C++中,在默认的情况下,当函数返回一个值时: return expression ;
expression 被求出值,并将该值拷贝到临时存储空间,以便函数调用者访问。这种返回方式称为传值调用(return by value)<详见第三讲>。 例:当调用如下函数 int val1(){ … //函数的主体 return i; } 这时i的值将被拷贝到临时存储空间,调用者获得是i的一个副本.也就是说,如果有调用函数: j=val1(); 升达大学资讯系
17
4.函数与引用(续) 临时存储空间 8 i j 拷贝到 则将i的值拷贝到临时空间,然后再拷贝到j。如下图所示
函数返回值还有另一种方式,即引用返回(return by reference).在这种情况下,返回值不再拷贝到临时存储空间,甚至连return语句所用的那个存储单元对调用者来说都是可访问的。引用返回的语法是在返回类型前加一个标记“&”. 升达大学资讯系
18
4.函数与引用(续) 8 i j 拷贝到 例:假设有一个函数定义如下: int& val2(){ … //函数的主体 return i;}
由于上述函数的返回类型为int& ,因此是引用返回。当return 语句执行后,调用者可以直接访问i.例如,以下方式调用函数val2(): j=val2(); 则将i的值直接拷贝到j中,如下图所示: 8 i j 拷贝到 升达大学资讯系
19
4.函数与引用(续) 例:很多程序员习惯从1开始的数组,如下函数则可以实现访问从0开始的数组,其方式对于程序员来说是访问从1开始的。该函数以I为索引访问整型数组a,其中是从1开始的,同过减1操作,寒暑内部实际是以C++方式(即从0开始索引)访问该数组。函数返回的是数组元素的引用。 int& new_index(int a[],int i){ return a[i-1]; } int& 标记表明函数的返回类型是int型的引用,返回的是实际单元a[i-1],而不是其副本。可用如下方式调用函数new_index: val=new_index(a,8);//返回的是第8个数组元素 升达大学资讯系
20
4.函数与引用(续) 回头看看 -16被保存在数组第8个单元中即a[7]中
使用引用返回的一个好处是:如果一个函数以引用方式返回,则这个函数调用可出现在赋值语句的左边。例如以下为对函数new_index的合法调用: new_index(a,8)=-16; 其含义是: -16被保存在数组第8个单元中即a[7]中 由于使用引用的函数返回的是一个实际单元,所以必须保证函数返回时该单元仍然有效。也就是说在定义引用的函数时,注意不要对该函数内的自动变量的引用。不然的话,因为自动变量的生存期仅限于函数内部,当函数返回时,自动变量就消失了,函数就会返回一个无效的引用。 升达大学资讯系
21
4.函数引用的一个例子 运行结果 #include<iostream.h> int a[]={2,4,6,8,10,12};
int& index(int i);//引用函数的声明 void main(){ cout<<"原来a[3]的值为:"<<a[3]<<endl; index(3)=16; cout<<"执行index(3)=16后a[3]的值为:"<<a[3]<<endl; cout<<“执行index(3)=16后index(3)的值为:"<<index(3)<<endl; } //================引用函数的定义=========== int& index(int i){return a[i];} 升达大学资讯系
22
4.函数引用的另一个例子 函数: int& f(){ int i; //…
//…*****ERROR:i goes out of existence rerutrn i; } 上述语句包含一个错误。当函数f()返回i时,i已经不存在了(因为i是局部变量)。因此,函数调用者不能访问传回的i。 纠正上述错误的两种方法: 1.改为传值调用,此时i的值被拷贝到临时空间中,函数调用者从临时变量中访问该临时变量。 2.将i声明为全局变量,则I的值不会因为f()的调用结束而结束 升达大学资讯系
Similar presentations