360 likes | 523 Views
面向对象的 C++ 程序设计 基础. 第 6 章 模板. 本章主要内容. 1 函数模板 2 类模板. 模板的引入. 程序设计中,当参与运算的数的不同值会随实际情况而变化时,就使用变量来代替这个数。同理,当处理的数据类型随实际情况变化时,可将数据类型作为可变的部分(参数)从程序中抽取出来。当出现真实的数据类型时,再用具体的数据类型代替; 模板就是为解决这个问题而产生的; C++ 中对数据进行处理的主体可以是函数,也可以是类,因此模板包括函数模板、类模板。. 模板概述. ● 在 C++ 标准库中,几乎所有的代码都是模板代码。
E N D
面向对象的C++程序设计基础 第6 章 模板
本章主要内容 1 函数模板 2 类模板
模板的引入 • 程序设计中,当参与运算的数的不同值会随实际情况而变化时,就使用变量来代替这个数。同理,当处理的数据类型随实际情况变化时,可将数据类型作为可变的部分(参数)从程序中抽取出来。当出现真实的数据类型时,再用具体的数据类型代替; • 模板就是为解决这个问题而产生的; • C++中对数据进行处理的主体可以是函数,也可以是类,因此模板包括函数模板、类模板。
模板概述 ●在C++标准库中,几乎所有的代码都是模板代码。 ●模板是对具有相同特性的函数或类的再抽象,模板是一种参数化的多态性工具。 ●所谓参数化多态性,是指将程序所处理的对象的类型参数化,使一段程序代码可以用于处理多种不同类型的对象。 ●采用模板编程,可以为各种逻辑功能相同而数据类型不同的程序提供一种代码共享的机制。
模板 ●模板并非通常意义上可直接使用的函数或类,它仅仅是对一族函数或类的描述,是参数化的函数和类。 ●模板是一种使用通用类型参数来产生一族函数或类的机制。
模板 函数模板 类模板 参数实例化 参数实例化 模板函数 模板类 对象 … 对象 模板与实例的关系
模板的声明 • 模板声明格式: template<模板参数表> 模板定义体 • template为声明模板的关键词,声明一个参数化的类或函数集; • “模板参数表”表示一个或多个模板参数,每个模板参数由关键字class和模板形参两部分组成,多个模板形参之间以“,”分割; • 模板定义体是该模板的作用体,可以是函数或类的定义。
函数模板 • 函数模板和模板函数 • 重载模板函数
函数模板和模板函数 函数模板 • 请先看交换两个数据的函数例子: 1、交换两个整数 void swap(int &n1, int &n2) { int temp; temp=n1; n1=n2; n2=temp; } 2、交换两个长整型数 void swap(long &n1, long &n2) { long temp; temp=n1; n1=n2; n2=temp; }
函数模板 • 上述swap函数是重载函数,它们的参数个数相同,实现算法相同,仅参数类型不同; • 重载时需要分别定义不同参数类型的版本; • 能否为上述的重载函数编写一个通用的算法的函数,使其使用多种数据类型?
函数模板 • 函数模板给出了解决问题的办法:将数据类型抽取出来作为函数参数; • 函数模板是一个通用函数,其函数类型和参数类型不具体指定,用一个“通用的”类型来代替; • 凡是函数体相同的函数都可以用此模板代替,不必定义多个函数。
函数模板的定义 • 调用函数模板时,系统根据传递来的实参类型取代模板中的通用类型,实现不同的函数功能; • 函数模板是对通用函数的描述,不是一个实在的函数,编译系统不会为其产生任何代码; • 函数模板将函数要处理的某种特定的数据类型说明为参数,表示它能对通用的数据类型进行处理。 • template <模板参数表> • 返回类型 函数名(参数表) • { //函数定义体}; 函数模板
函数模板使用注意 • 函数模板中,template是关键字。模板参数是函数模板的参数化的数据类型,例如: template <class T> • void swap(T &n1, T &n2) • { T temp; • temp=n1; • n1=n2; • n2=temp; • } • 函数模板只是说明,不能直接执行,需要实例化为模板函数后才能执行。 函数模板
函数模板使用注意-2 • 若编译系统发现以下调用:函数名(实参表),则用数据的实际类型代替参数表中的通用类型,并生成与函数模板定义的函数体相同的一个重载函数,即模板函数; • 例如执行 “int a, b; swap(a,b);”后,将使类型形参T被实例化为整型int,同时产生如下的重载函数: • void swap(int &n1, int &n2) • { int temp; • temp=n1; • n1=n2; • n2=temp; } 例6.2.1 函数模板应用举例。 函数模板
函数模板使用注意-3 函数模板 • 在函数模板被实例化以前,必须先对函数模板进行声明(可能不进行定义)。 • 例6.2.2 :设计函数模板sort<T>,采用直接插入排序方式对数据进行排序,例如整数序列 和字符序列。
重载模板函数 函数模板 • 虽然函数模板中的模板形参T可以被实例化为各种类型,但实例化T的各个模板实参之间必须保持完全一致的类型,否则会出错; 例6.2.3:分析程序的运行结果。
重载模板函数 函数模板 • 对于个别实参类型不一致的情况,可以采用非模板函数重载模板函数的方法,即对个别类型重新定义一个非模板函数,有两种情况: 1、只声明 一个非模板函数的原型; 2、定义一个完整的非模板函数。
1、只声明一个非模板函数的原型 这种情况只声明原型,不给出函数体,它借用函数模板的函数体; 此重载函数支持数据类型间的隐式转换(因为它是一个普通的外部函数); 执行此重载版本时自动调用函数模板的函数体。例如: int min (int, int); //非模板函数的原型 void main( ) { int n=2; char c=‘a’; cout<<min(n,n)<<endl;//模板 cout<<min(c,c)<<endl;//模板 cout<<min(n,c)<<endl;//重载 cout<<min(c,n)<<endl; //重载 } Template <class T> T min(T x, T y) { return (x>y)?y:x; }
2、定义一个完整的非模板函数 这种情况定义的重载函数,所带的参数可以随意,就像一般的重载函数。如: char *min(char *x, char *y) { return strcmp(x, y)>0) ? y:x; } 此模板函数min(char *x, char *y),就重载了上述的函数模板; 如果出现调用语句:min(“123”,“abc”); 便执行上述重载的非模板函数。
重载非模板函数要避免二义性 • 例如: • int max(int, int); //非模板函数的原型 • char max(int x, char y) //定义完整的非模板函数 • { • //用户自定义的函数体 • } • 当进行函数调用“max( i, f );”时,存在二义性。因为,利用函数模板的函数体重载时支持数据间的隐式类型转换。 • 如何避免?用函数模板的函数体重载时,就不要再重新定义重载函数体了。
函数调用的一般规则: • 函数模板与同名的非模板函数重载时,函数调用遵循以下约定: 1. 寻找一个参数完全匹配的函数,如果找到了就调用它; 2. 失败后,寻找一个函数模板,使其实例化,产生一个匹配的模板函数,若找到了,就调用它; 3. 若1、2都失败了,再试一试低一级的对函数的重载方法,例如通过类型转换可产生参数匹配等,若找到了,就调用它。 4. 如果1、2、3都未找到匹配的函数,则是一个错误的调用。
函数模板和模板函数的区别: 函数模板 • 函数模板不是函数,定义中使用的是通用类型的参数,编译系统不为其产生运行代码; • 函数模板只是定义了对数据的加工逻辑,并没有确定真正的数据类型,也就无法生成实际的执行代码; • 模板函数是函数模板被调用时根据参数类型生成的实际函数,包含最终的执行代码; • 模板函数中使用的数据类型都是实际类型; • 模板函数出现在函数调用执行过程中,一般不能显式地看到。
函数模板小结 • 模板:旨在于完成一些通用算法。采用模板功能后,即可在模板内将数据类型抽象为一种广泛类型,在忽略数据类型差异的情况下设计模板,只需要考虑具体算法。反之,用户使用模板时需要用特定的数据类型代替模板中的抽象数据类型,而不必考虑具体的算法; • 函数模板:参数类型、返回值类型或函数体中使用的类型是通用类型的函数称为函数模板,它定义了一类函数。函数模板实例化可得到模板函数。模板函数的重载有两种方法。
本章主要内容 1 函数模板 2 类模板
类模板与模板类的概念 类模板 • 一个类模板允许用户为类定义一种模式,使得类中的某些数据成员、某些成员函数的参数或某些成员函数的返回值,能取任意类型; • 如果一个类中数据成员的类型不能确定,或者某个成员函数的参数或返回值类型不能确定,就必须将此类声明为模板; • 类模板的存在不是代表一个具体的、实际的类,而是代表着“一类”类。
类模板的定义 类模板 • 定义一个类模板: template <模板参数表> //声明一个模板 class 类名 { //类模板体定义 }; • 其中<模板参数表>与函数模板中的意义相同; • 类成员的声明方法同普通类的定义,只是各个成员(数据和函数)可以用模板的通用类型参数。
例:template<class T> class database { T *data; int size; public: database (int); T & operator[ ](int); … };
类模板的定义 类模板 • 定义类的成员函数:在类模板定义体外定义类模板的成员函数时,需在函数体外进行模板声明: Template<模板参数表> <返回类型> 类名<类型名表>::成员函数名(参数表) { //成员函数定义体 } 其中类型名表是类型形参的使用; • 注意:类模板的所有成员函数一定是函数模板,因为类的声明体内采用了模板参数,而成员函数可以对数据成员进行操作,那么成员函数必须是函数模板才行。
例6.3.1 声明一个类模板 类模板 template <class T1, class T2> class Example {public: T1 getx( ) {return x; } T2 gety( ) {return y; } private: T1 x; T2 y; }; • 类模板Example中T1、T2为类型参数; • 类模板不能直接使用,必须先实例化为具体的类,才能用来定义对象。
类模板的使用 类模板 • 类模板的使用实际上是将类模板实例化成一个具体的类---模板类,它的格式为: <类名> <实参表> 对象表; • 其中<实参表>应与该模板中的<模板参数表>匹配,模板类是类模板实例化后的产物,如下图所示:
类模板Example<T1,T2> 实例化 实例化 实例化 模板类 Example<char,int> 模板类 Example<int,double> 模板类 Example<char,double> 实例化 实例化 实例化 对象a2(4,3.2) 对象a1(‘c’,1) 对象a3(‘d’,3.5)
与函数模板不同的是,类模板在实例化对象时,显式地说明了参数类型的实际类型,而函数模板是在调用时由编译程序根据实参的类型来决定函数参数的类型。与函数模板不同的是,类模板在实例化对象时,显式地说明了参数类型的实际类型,而函数模板是在调用时由编译程序根据实参的类型来决定函数参数的类型。 • 例6.3.2: 分析程序的执行结果。
类模板的派生 类模板 • 类模板可以作为基类派生出新的类模板,即可以派生类模板; • 派生类模板声明注意: 1、在声明前加上“Template <模板参数表>”; 2、在指出基类时缀上模板参数; 3、其余的与一般派生类定义相似。 • 例6.3.3 类模板派生的例子。
#include <iostream.h> template <class T> class A { public: void showA(T a){ cout << a << endl;}}; template <class X, class Y> //类模板的派生 class B: public A<Y> {public: void showB(X b){cout << b << endl;}}; void main() { B <char, int> bb; bb.showA(10); bb.showB(‘A’);}
类模板小结 • 类模板:数据成员类型、成员函数的参数、返回值或函数体中使用的类型为通用数据类型的类称为类模板,它定义了一类类。 • 类模板可以派生出另一个类模板。
本章小结 • 模板:旨在于完成一些通用算法,如数据结构中的栈操作、排序操作和树操作。这些操作在不同的应用中有不同的数据类型。采用模板功能后,即可在模板内将数据类型抽象为一种广泛类型,在忽略数据类型差异的情况下设计模板,只需要考虑具体算法。反之,用户使用模板时需要用特定的数据类型代替模板中的抽象数据类型,而不必考虑具体的算法; • 函数模板:参数类型、返回值类型或函数体中使用的类型是通用类型的函数称为函数模板,它定义了一类函数。函数模板的实例化和模板函数的重载(两种方法); • 类模板:数据成员类型、成员函数的参数、返回值或函数体中使用的类型为通用数据类型的类称为类模板,它定义了一类类。类模板可以派生出另一个类模板。