C++语言程序设计6第六讲——模板_第1页
C++语言程序设计6第六讲——模板_第2页
C++语言程序设计6第六讲——模板_第3页
C++语言程序设计6第六讲——模板_第4页
C++语言程序设计6第六讲——模板_第5页
已阅读5页,还剩117页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

模板,C+语言程序设计,本章主要内容,函数模板 类模板 (模板以外的内容已不属于语言,而是应用。),在编程中程序员经常会遇到这样的情况:当需要实现某函数功能时,由于需要处理不同类型的数据,于是不得不定义多个很相似的函数。例如编写求两个整型数据或实型数据中最大值的函数,它们的程序逻辑相同,程序代码也相同,只是参数类型与返回值类型不同。对于这种情况,C语言程序员不得不定义两个不同名函数,重复书写函数代码。同样也会遇到具有类似功能的类,例如一个整型数据集合的类与实型数据集合的类,它们实现的功能相同,但存储的数据类型不同。 对于上面的情况,C+语言中可以使用模板来避免重复书写相同的代码。,概念导入,C+最重要的特征之一就是“代码重用”; 代码要想重用,就得通用,通用的一大障碍就是类型限制。若代码能自动适应类型的变化,通用进而重用就成为可能; “自动适应类型的变化” 就是将确定的类型变成了不确定的东西,函数的参数就是这种不确定的东西,将类型参数化是解决途径。这是继值、地址参数化之后,将类型也参数化的技术,叫做模板。模板就是类型的参数化技术; C+的模板是1991年才加进去的;,何谓“模板”,有一则笑话,在保温瓶刚发明时,一美国工程师非常惊奇:放进热东西它保热,放进凉东西它保凉,它怎么知道的?,C+的模板分为函数模板和类模板; 模板代表了一个代码家族: 函数模板代表了一个函数家族; 类模板代表了一个类家族; 模板引入的类型的抽象,是泛型程序设计的基础,在STL中得到最广泛的应用。 泛型编程技术与面向对象的编程风格在编程思想上是相左的,面向对象风格表现了数据和操作被捆绑封装为一体;而泛型编程技术则极力将数据和操作分离、独立。,模板其实就是“代码生成器”,模板使用模型,二级实例化,模 板,对象1,模板函数,对象n,类模 板,函数模 板,模 板类,二级实例化,一级实例化,一级实例化,模板的实例化与类的实例化是完全不同的两个概念,不可混淆; 模板的实例化又被称为具现(instantiation)。是产生代码的过程;因此一个模板实际代表了一个“家族”,对于类模板和函数模板都是如此。 类的实例化被称为实现(implement)。是产生内存对象(变量)的过程,产生的是数据;,两种“实例化”,( 这里所讲实例化都是前图中的一级实例化 ) 实例化:把模板中的形式模参用实模参替代而产生一段新代码的过程; 实例化的工作内容:目标代码的生成及重命名、类型检查、避免重复的优化等; 请注意函数及类的重命名。函数的重命名是隐含的,类的重命名是显式的:类名,何谓“实例化”具现,没有模板的年代,编译器与链接器是分工合作的,编译器只处理声明和定义,调用是由链接器处理的。 引入模板概念后,这种工作模式发生了巨大变化。编译器要插手使(调)用了:它要知道函数将来是怎样调用的,以便于生成相应的函数代码;要知道类是如何创建对象的、调用了哪个成员函数,以便于实例化出适当的类来。于是你必须让它“看到”如何使用。,模板对编译器的影响,由于模板概念的引入,“参数”的含义丰富了。它分为两种: 模参 在模板头中声明,代表类型,叫类型参数; 值参 在函数头中声明,代表值,叫数据参数; 请务必区分开。 这两种参数在函数模板或类模板中通常是相关的,但在类模板中可以是完全独立而不相关 。,模板的参数,函数模板是一类相同功能但不同类型函数的制造模型,可用来创建多个不同类型的函数,这将大大简化代码的设计。 函数模板不再是函数,是模型。 声明方法:,函 数 模 板,template 函数声明,模板头,函数模板,参数表,函数模板,求绝对值函数的模板,#include using namespace std; template T abs(T x) return x0?-x:x; void main() int n=-5; double d=-5.5; coutabs(n)endl; coutabs(d)endl; ,函 数 模 板,运行结果: 5 5.5,求绝对值函数的模板分析,编译器从调用abs()时函数实参的类型推导出函数模板的类型参数。例如,对于调用表达式abs(n),由于实参n为int型,所以推导出模板中类型参数T为int。 类型参数的含义确定后,编译器以函数模板为样板,生成模板函数: int abs(int x) return x0?-x:x; 这是个实实在在的函数,是函数模板实例化产生的模板函数。,函 数 模 板,又例: #ifndef MINMAX_H #define MINMAX_H /避免重复包含本文件 template T max(T a,T b) return (ab)?a:b; template T min(T a,T b) return (a int inrange(T a,T b,T c) / ”在其中”函数 return (b=a) #endif,该程序中声明并实现了三个函数模板,每个函数模板都以模板头开始。下面的程序使用了已定义的模板,可以看出如何使用函数模板: #include #include “minmax.h” void main( ) int a=123, b=456; cout“min(123,456)=“min(a,b)n; cout“max(123,456)=“max(a,b)n; double c=3.14159, d=9.87654; cout“min(3.14159, 9.87654)=“min(c,d)n; cout“max(3.14159, 9.87654)=“max(c,d)n;,short s1=10, low=5, high=15; cout“s1=“s1n; if(inrange(s1, low, high) ) coutlow“=s1=“highn; else cout“s1 is not within range“ low “to“ high “n“; 可以看出,函数模板在使用时没有显式地指明其参数类型。程序中两处用到min模板,编译器根据参数的数据类型进行匹配。第一次使用min模板,其参数为整型数据,编译器按模板生成下面形式的函数: int min(int, int);,第二次使用参数为双精度型数据,编译器按模板生成下面形式的函数: double min(double,double); 这里编译器使用了函数的重载机制,生成两个同名的函数,它们的参数类型不同。因此无须显式声明在套用模板时如何给定模板的参数。,编译器对代码中的函数模板记下了函数框架; 扫描到调用表达式时,按实参类型在代码区生成模板函数的代码,即此时才将模板实例化; 运行时再将实参的值或址由形实结合传给已生成的模板函数; 若是不同类型的多次调用,编译器将产生多段模板函数的代码,它们是重载关系; 若是同一类型的多次调用,编译器将只产生一段模板函数的代码,这是其优化功能的功劳 。,函 数 模 板,编译器的工作,编译器在具现函数模板时,要完成: 1. 将实参类型绑定给模形参; 2. 对函数名施以“碾压”手术,使之产生一个独一无二的函数名函数签名( signture ) ; 正是由于函数签名,才能实现函数重载。,函 数 模 板,若将函数模板存于头文件,则应将函数原形和函数实现完整存于一个文件中,不可头体分离; 编译器在将函数模板实例化成模板函数时,严格按实参类型作为实例化依据,推断出函数模板的模形参的类型,决不做自动类型转换; 普通函数若与函数模板同名,也属重载,甚至普通函数可与函数模板共用函数体; 模板头所列类型参数必须悉数使用,不用属语法错; 不可为返回类型单独设定模板参数 。,函 数 模 板,使用函数模板的要点1,模板中的每个模参在函数参数表中必须至少使用一次。下面的声明是不允许的: template void f(T1 param) /.函数体中没再使用T2 这个函数模板声明了两个参数T1和T2,但是函数本身却只使用了T1来定义param。正确的声明应该是在函数参数表中至少使用T1和T2各一次:,使用函数模板的要点2,普通函数可以在头文件中声明原型,而将函数实现放在另外的独立源文件中,在使用时只需将头文件中声明函数原型,而不需要函数实现的源代码。 但这对于模板行不通。目前的编译器一般不支持将函数模板的声明与函数模板的函数体部分分别存放,然后通过include来使用,而必须放在同一个文件里(可以都放在头文件中,也可以是源文件中)。比如将一个函数模板声明及实现的头文件命名为minmax.h。在不同的程序文件包含它,可以在需要时使用它。,使用函数模板的要点3,#include template /定义了两个形参的函数模板 T max(T x,T y) return xy?x:y; template /定义了三个形参的函数模板 T max(T x,T y,T z) T w=x=y?x:y; return wz?w:z; template /定义了数组为形参的函数模板 T max(T x,int n) int i; T ma=-0.001; /足够小的值 for (i=0;in;i+) if (maxi) ma=xi; return ma; ,函数模板及其重载,void main() float x=65,y=-66.8,z=98; int data10=1,2,33,4,5,6,57,8,9,10; cout“x=“x“ y=“yendl; cout “x与y 比较 : “max(x,y)“ 大!“endl endl; /使用两个形参的模板函数 cout“x=“x“ y=“y“ z=“zendl; cout“x与y,z 比较 : “max(x,y,z)“ 大!“ endlendl;/使用三个形参的重载的模板函数 cout“整个数组比较 : “max(data,10)“大!“endlendl;/使用数组为形参的模板函数 cout“数组的前4个元素比较 : “max(data,4)“ 大!“endlendl; ,#include #include template /定义了比较大小的函数模板 T max(T x,T y) return (xy)?(x):(y); /定义了比较大小的“压制“函数 char * max(char * x, char * y) return (strcmp(x,y)?(x):(y); ,函数模板与“压制“函数,void main() char * c1=“FileSave“; char * c2=“NewFile“; cout“c1=“c1“ c2=“c2endl; cout“c1与c2比较: ”max(c1,c2)“ 大 !” endl; /调用“压制“函数,模板函数没有调用 “压制”: C+语言允许在定义了一个函数模板后,以特定的函数来顶替该函数模板。,先寻找有没有类型完全匹配的函数,有则调用,没有则下一步;若多于一个则出错。(压制就发生在此处。) 然后寻找有没有匹配的模板函数,有则调用,没有则下一步; 最后寻找有没有将实参类型转换后可调用得函数,有则调用,没有则出错。 可见模板对函数参数的匹配是很苛刻的:不做类型转换,只是完全匹配才会具现出可供调用的函数。,编译器进行函数匹配的顺序 :,#include template void swap(T / error。s1、s2甚至不被当作指针,它们 /的类型被视为char5和char7,这是两个不同的类型,严格匹配的例子,当模实参类型为char5时,此句便成为char temp 5 = a;这将导致数组初始化非法的错误。,#include #include template T m(T a, T b) couta“ - “bendl; return 0; /这里使用仅有函数头的压制函数。 /int m(int, int); / 若将压制函数放在这里,出错!系统会提示: error C2782: T _cdecl m(T,T) : template parameter T is ambiguous could be int or char 模糊的,“压制”函数要放对位置,void main() int i=10; char c=a; m(i, i); /调用max(int, int) m(c, c); m(i, c); /因没有可匹配的max(int, char),因此出错。 m(c, i); /也没有可匹配的max(char, int),因此出错。 ,若将压制函数放在这: int m(int, int);,m(111,222); /若增加这样的显式调用函数 int m(int, int); /再配合压制函数,则可正常调用 !,则此句调用max(char, char) 显示 a-a,则会什么都不显示。 因为该函数没有函数体, 没有可执行的代码。 由于它的作用,就阻碍 了模板函数的正常调用 !,int m(int, int); /压制函数放在这也出错,因为声明的太晚了, 使m(i, c)无法执行 。,int m(int, int); / 压制函数放在这里,正确 !,10-10 a - a 10-97 97-10,111-222 10-10 97 - 97 10-97 97-10,则此句调用max(int, int); 从而显示 97- 97,#define MAX(x,y,z) (xy)?(xz?x:z):(yz?y:z) 若定义了:char ch = a; int i = 100; double d = 101.34; 有语句调用: cout T MAX(T x,T y,T z) return (xy)?(xz?x:z):(yz?y:z); 编译器就会对实参进行静态类型检查,给出 “类型不匹配”的出错提示,这样就不会漏掉不安全因素。,函数模板解决了宏的数据不安全问题,函数模板实例化成模板函数时,有两种方式:隐式和显式 。 如有 templete T max(T a, T b); 用 max(4, 8); 调用,则属于隐式实例化; 用 max(4, 8); 调用,则属于显式实例化. 后一种实例化其实是指明了具现的目标 ,即打造一个double max(double , double)函数。这使我们联想到static_cast的使用。,函 数 模 板,函数模板的实例化方式,还可以用 max(4, 8); 的方式调用函数模板,这种方式是在告诉编译器:只能用模板来匹配,纵然有重载的压制函数 int max( int, int );也不能用,所有的模形参(typename T)的类型必须靠实参4和8的类型推导出来。 这叫“显式指定空模参表的调用”,这也属于显式实例化。,函 数 模 板,又一种模板实例化的方式,还用前面的例子: void main() int i=10; char c=a; m(111,222); /若增加这样的显式调用函数 int m(int, int); /压制函数放在这里则使m(c, c)显示97-97 m(i, i); /调用max(int, int) m(c, c); /调用max(int, int) 显示“ 97-97” ,m(111,222); /若只写这样的显式调用函数,则调用max(char, char) 显示“a - a ”,template void show(void) /声明一个无参的函数模板 T x=10,y=20; cout (); show(); ,无参函数模板的调用,函数模板的函数头中没有出现模参,用隐含调用的方式无法将模实参带入函数。故必须用显式调用方式。,#include template void swap(T / 同上 ,引用性模形参不可用常量作实参,会以const int匹配函数模板中的T,于是a、b都成了常量,于是 a=b; b=temp; 是非法操作。,templete inline const T 这样编译器就会将该函数实例化后的代码在各调用处一一展开。,模板函数可以内联,若定义过: # define fswap (T) void swap(T / . 但这仅是模仿,因为没有类型检查,没有优化,没有压制 . 。因此绝非模板。,函 数 模 板,参考:用宏模仿函数模板,隐含实例化:是实例化出模板函数并且调用它。由编译器对参数类型进行精确匹配,不进行任何隐含的类型转换。用于首次使用模板时。 如:coutMAX(a,b,c); 显式实例化 :使用 template 或 typedef 后跟模实参的形式声明模板时; 如:template int MAX(int,int,int); 其实仅是在指示编译器实例化出模板函数,尚未调用。,函数模板实例化分类,template T max(T a, T b) return (ab)?a:b; void f ( int i, char c) max(i, i); /调用max(int, int) max(c, c); /调用max(char, char) max(i, c); /没有匹配的max(int, char) max(c, i); /没有匹配的max(char, int) 这段代码在编译时将会产生以下的错误信息: Could not find a match for max(int, char) in function f(int, char) Could not find a match for max(char, int) in function f(int, char),改正: 可以显式地声明一个原型为 int max(int, int)的函数进行引导(甚至可以不写函数实现)。用于将函数调用max(i,c)和max(c,i)中的c隐含转换为整型数据后传递给max,于是,语句max(i, c)和max(c, i)将可以通过编译并正常运行。 void f(int i, char c) max(i, i); /调用max(int, int) max(c, c); /调用max(char, char) int max(int, int) ; max(i, c); /调用max(int, int) max(c, i); /调用max(int, int) ,对于函数模板: template inline RT max(T1 a,T2 b) /内联的函数模板 return ab ? a:b ; 可以用 max (4, 4.37); 的方式,明确给出各模形参的全部类型,这叫完全显式调用函数模板。 也可以仅给出部分模形参类型:,函数模板显式调用的变化1,请注意:次序定要符合模板头中的声明次序 。,函数模板必须如下: template inline RT max(T1 a,T2 b) /内联的函数模板 return ab ? a:b ; 调用时可以用 max (4, 4.37); 的方式。这种只给出模形参的部分类型的方式叫部分显式调用函数模板。 显然,没显式给出的模形参类型必须是由值实参隐含给出的,而且RT应放在模板参数表的最左侧。,函数模板显式调用的变化2,来看一个例子: #include #include #include template / A inline T const ,如果max(a,b) 函数使用传值调用,则出错。,注意千万不要将该函数放到第三个函数后面。,void main () :max(7, 42, 68); / OK const char* s1 = “frederic“; const char* s2 = “anica“; const char* s3 = “lucas“; :max(s1, s2, s3); / 错误! ,最后一句,如果没有B段,结果为 68 frederic。编译可以通过,但结果是错的。因串frederic的址大。 如果三段都有,VC6给出警告,结果是乱码。由于 return max (max(a,b), c)中max(a,b)调用的是B段,返回的是局部的临时变量的引用,该值被外面的max以引用的方式传出,将导致无效的引用,故是乱码。,只有给程序再补上一段: template / D inline T const* max (T const* a, T const* b, T const* c) return max (max(a,b), c); 该函数的执行结果才对。,若模板有错,只能实例化后才能显现,又得映射给模板来显示错误。过程曲折复杂,且提示晦涩难懂; 编译过程费时且不可中断,若中断则会造成链接错误; 可作函数模板的有:全局函数、类(普通类或模板类)的成员函数。 函数模板不能像类模板那样支持数据参数,也不支持默认类型参数。,由模板带来的复杂性,类模板,同样,类模板不是类,是一类相同属性和功能但不同类型的类的制作模型。用户使用类模板可以为类声明一种模式,使得类中的某些数据成员的类型、某些成员函数的参数类型、某些成员函数的返回值类型,都能取任意类型(包括基本类型的和用户自定义类型)。 可用来创建成员布局相同的多个类,可大大简化代码的设计。应用模板最大得利者是STL。它使容器可容纳各式各样类型的元素。,类 模 板,类模板的声明,类模板:,类 模 板,template class 类名 类成员声明;,template 类型名 类名:函数名(参数表) 函数体 ,如果需要在类模板外定义成员函数,则要采用以下的形式:,例9-2 类模板应用举例,#include #include using namespace std; / 结构体Student struct Student int id; /学号 float gpa; /平均分 ;,类 模 板,template /类模板:实现对任意类型数据进行存取 class Store private: T item; / 用于存放任意类型的数据 int haveValue; / 用于标记item是否已被存入内容 public: Store(void); / 默认形式(无形参)的构造函数 T GetElem(void); /提取数据函数 void PutElem(T x); /存入数据函数 ; / 默认形式构造函数的实现 template Store:Store(void): haveValue(0) ,51,“类模板”类名,每个成员函数实现时,无论是否使用到模参,都必须带模板头,亦可写为: Store(void); 在类内可以省略,template / 提取数据函数的实现 T Store:GetElem(void) / 功能:如果试图提取未初始化的数据,则终止程序 if (haveValue = 0) cout / 存入数据函数的实现 void Store:PutElem(T x) haveValue+; / 将haveValue 置为 TRUE,表示item中已存入数值 item = x; / 将x值存入item ,52,void main(void) Student g= 1000, 23; Store S1, S2; Store S3; Store D; S1.PutElem(3); S2.PutElem(-7); cout S1.GetElem() “ “ S2.GetElem() endl; S3.PutElem(g); cout “The student id is “ S3.GetElem().id endl; cout “Retrieving object D “ ; cout D.GetElem() endl; /输出对象D的数据成员 / 由于D未经初始化,在执行函数D.GetElement()时出错 ,53,运行结果: 3 -7 The student id is 1000 Retrieving object D No item present,编译器对代码中的类模板记下了类框架; 扫描到创建对象时,按模板的实参类型生成模板类的代码,即此时才将类模板实例化; 运行时再用已生成的模板类创建对象; 若是用多种不同类型实例化对象,编译器将产生多段模板类代码。 此时的具现仅完成了类模板的定义,并未对其成员函数具现,直到某函数被调用时才具现它,也就是说具现是分两步做的。这不同于函数模板。,类 模 板,使用类模板时编译器的工作,类模板不是类,仅是产生类的模型;模板类才是类,,类模板在使用时与函数模板不同。函数模板无须显式指明使用模板时的参数(使用了函数重载机制),当然必要时也可以显式指明; 模板类在使用时必须指明模实参。形式为: 类模板名 例子中Store S1, S2; Store S3; 即是如此,编译器会根据参数int生成一个int的Store类。我们将Store称为模板类名。在使用时Store不能单独出现,它总是和尖括号中的参数表一起出现。,隐含实例化:在类模板声明后首次遇到的对应的模板类使用时,且模板类是不相同的,就会具现它。 如:MyArray a; 该语句将经历两级实例化,创建起对象a。 对于 void output( MyArray 编译器将它们视为不同的模板类,从而具现成不同的类。 当然只具现了类,没有具现成员函数。,类模板实例化的分类,显式实例化,如: template class MyArray; 这一句只完成一级实例化(没造对象),打造出模板类。 还可以使用 typedef 关键字显式实例化: typedef MyArray MyIntArray; 这句也是实例化出模板类 MyArray , 又名叫 MyIntArray 。此时的typedef除重命名外还承担了具现任务。 这种实例化方式效果不同于前者:会将类的所有成员函数都实例化出来显式实例化的具现不再分两步做,而是一古脑将类模板的所有成员函数都具现成模板函数。,若将类模板存于头文件,则应完整存于同一个文件中,不可头体分存多处,以便于“一股脑包含”; 若声明了多个模参,则生成对象时不可代换成相同类型;( why?) 类模板的成员函数在类外实现时所带的类名限定要处处含有模实参; 类成员函数模板不可为虚函数;但类模板的普通成员函数可以是虚函数;( why?) 类模板成员函数的模形参不可含有数据默认值,但数值形参可以; (why?) 类模板的模参可以含有类型默认值; 模板头所列类型参数必须悉数使用,不可空置;,类 模 板,使用类模板的要点1,类成员函数模板只有被调用时才会被实例化。言外之意就是,那些尚未使用的成员函数可以只有函数原形,没有函数实现。只要不调用到它就行。 其原因有三: 一是为了节省时间和空间; 二是对于“未能提供所有成员函数的全部操作”的类,也可以实例化成模板类。结果是“尽管类残缺,但照样不影响使用”。 三是成员函数可以是与模板类的模参不同的函数模板。,类 模 板,使用类模板的要点2,模板的参数表中既可以包含模参,也可以包含值参。 比如: template class bitset; 就是声明了一个类模板bitset ,它能具现出不同的模板类。(函数模板也可以带值参。) bitset a; bitset b; 值得注意的是,a和b是两个不同类型的对象,它们不可以相互转换和赋值。 a = b; / 错误!,类 模 板,模板的参数表,尽管都叫实例化,可操作的时机、工作内容、采用的策略、产生的结果皆不相同。 具现的结果是类,是代码,不是内存实体;会产生多个类型; 实现的结果是对象,会产生多个内存实体; 具现类模板时,不管是否要被调用,都会将成员函数实例化成模板函数; 实现类时,对于没有调用的成员函数忽略; 具现出来的类型马上供编译时实现用; 实现出来的对象是在运行时用的;,类 模 板,类模板两级实例化的差别,#include template class Ss private: T msize; /类的私有数据成员 public: Ss(T a,T b,T c,T d) /构造函数,使用了模板 m0=a;m1=b;m2=c;m3=d; T s() /一般函数成员,使用了模板 return (m0+m1)+m2+m3; ;,模板参数表中含有常量表达式,模参表含有默认值(常量表达式),这叫数据参数。常用于指定容器大小。,对于模参表含有默认值的类模板,其成员函数在类外实现时,不可再写默认值。,void main() cout i1(-23,5,38,-2); /模板类定义对象(包括常量表达式) Ss f1(3.4,-44.5,69.3,-29.1); Ss d1(355.4,256.2,98.23,-156.9); cout“整型数之和 :“i1.s()endlendl; cout“浮点数之和 :“f1.s()endlendl; cout“双精浮点数之和 :“d1.s()endlendl; cout“退出主程序 !“endlendl; ,由于类型参数在类模板生成模板类时已经明确给出,勿须编译器在实例化类模板时再对那些类型实例化,于是生成模板类时它(们)往往扮演着“指导者”的角色。这一点不同于数值参数。 但是要求那些类型能提供被用到的全部操作。 于是,模参可以带默认值。所带的默认值可以是基本类型,也可以是已定义好的扩展类型,甚至可以是另一个模板。,类 模 板,类模板的模参,template class MyArray T m_Alen; /类的私有数据成员 unsigneg long m_len; public: MyArray():m_len(len) T ,类模板的模参可含有类型默认值,这叫类型默认值,这叫值参默认值,类型参数和数据参数都可以带默认值。前者是类型名;后者是具体的值。带默认值的规则同前。,template class stack private: Sequence container; /类的私有数据成员 public: . ; stack mystack1; stack mystack2; stack mystack3;,模板参数的默认值是模板,这叫基于Sequence的栈。 是容器的适配器。,这叫类型默认值,这叫另一个模板充当模实参。,这会形成“二维栈”。,template class Array T m_a100; /类的私有数据成员 public: Array (); template void Print(X pa) / 正是由于分两步具现,才可以不同 ;,类模板的成员函数可以是另一种模板,该函数不可在类外实现。,template class Array T m_a100; /类的私有数据成员 public: Array (); template virtual void Print(X pa); virtual void Sort(); ;,类的成员函数模板不可为虚函数,出错!,但类中的普通成员函数可以是虚函数!,因为,若从Array派生了一个子类SelfArray ,并重写其成员函数Print,则重写版必然仍是模板,但基类的虚函数版本的实例化并不必然导致重写版按相同类型的实例化。,#include class Myclass int h,m,s; public: Myclass(int a,int b,int c); Myclass(); void showtime(); template void fun(T a,T b,T c) h=int(a);m=int(b);s=int(c); ; Myclass:Myclass(int a,int b,int c) h=a; m=b; s=c; cout“构造了一个对象 ! “endl; ,普通类的成员函数可以是模板,等价于为该类制作了不止一个成员,可以应付各种调用!,Myclass:Myclass() cout“对象被析构 ! “endlendl; void Myclass:showtime() cout“现在的时间是 : “h“ : “m“ : “sendl; void main() double x=10.233,y=33.457,z=56.942; Myclass clock(12,45,03); clock.showtime(); cout“n更改时间!n“; clock.fun(x,y,z); clock.showtime(); ,调用时没有什么特别之处。,问题: 比如:当你定义了const int NULL = 0; 之后,对于void f( int x );函数可用f(0)也可用f(NULL)调用,可对于void f( string * p);用f(NULL)则错误。 那么就定义成void *const NULL = 0; 这对于f(0)没影响,可对于f(NULL)又错了,类型不符。总没法兼顾两者。改用宏也没有起色:你总不能定义两个宏吧? # define NULL 0 # define NULL (void *)0) 对于形形色色的类型以及它们的指针,我们常常要用到NULL,而这些NULL要兼顾各种类型。于是要有个能适应各种类型的类模板。由于该类产生的对象是唯一的NULL ,所以模板类不必有名。,含有函数模板的无名类,const /用来修饰对象是个常对象 class /不必有名 public: template operator T*() const return 0 ; /该函数能将NULL转换成指向T类型的指针 template operator T C:*() const return 0 ; /能转换成指向C类的成员函数的指针,其返回类型是T类型 private: void operator /对象名是唯一的,NULL的类解决方案,类模板的出现使“类名”和“类型名”不再统一。 一个普通类,如class A ;它的类名和类型名都是A; 而类模板templete class A ; 中的A是类名,由构造、拷贝、析构等函数作函数名使用; A是类型名,由函数的形参和返回类型使用; (如 A ( const A ) 类中的成员函数在类外实现时,应使用类型名,因为类外实现的函数是实体,则必然是类实体而非类框架的成员。,类模板带来的复杂性,类中的成员函数在具现时,可能会发生“二次编译”(有的编译器是这样)。即由链接器的辅助工具重新激活编译器,将编译时未具现的函数完成具现。可见,类具现和成员函数具现实是分别进行的。 类模板中的成员函数仅是被调用的才会被实例化(节省时间)。更重要的是,对于未能提供完全操作的某些类型,也可以用作实例化的模实参,只要那些尚不完善的成员函数没被调用就行。,原因:若有一个类模板 如 类中的成员函数的类外实现如下: template T MyArray: sum () const usigned long count = len; T theSum = 0; while(count !=0) theSum +=m_Acount-1; count-; return theSum; ,类模板的特化,当我们用char*来实例化该类时,sum()函数将把len个地址累加,这显然不是我们要的结果。因此有必要针对char*类型人工写出该类及成员函数特定的实现代码。 这就叫 特化。 template /或写成 template class MyArray char * m_Alen; /类的私有数据成员 unsigneg long m_len; public: MyArray():m_len(len) char * operator (unsigneg long index) return m_Aindex; char * sum (char *) const; ; template char * MyArray: sum (char * buffer) const assert(buffer != NULL); for(unsigned long i =0;ilen;i+) strcat(buffer,m_Ai ); return buffer; ,特化就是对不确定的类型特别予以指出,使之确定下来,于是编译器就不再东猜西猜了。,可见类模板的特化就是针对某种特定的类型写出该类及成员函数特定的实现代码。即“特定的实现方案”。一般是出于对时空效率的优化或满足特殊需要而为之。 STL中的vector类就是对vector类的bool类型的特化。 还记得前面讲函数模板时讲到模板函数与“压制”函数吗? 那就是函数模板的特化。 使用时,模板特化的声明(定义)必须出现在基本模板之后; 特化版的优先级要高于基本模板即”压制”。 如:MyArray strArr; . coutstrArr.sum (buffer) ; /调用的是特化版,可以对已有的类模板中的模参稍加改造(不像特化那样给出确切类型),而是仍然使用抽象类型来特化原类模板,结果是要求用户在使用时再给出确切类型,又称为“偏特化”。 如现有类模板: template class MyClass / A . ; 可局部特化为: template class MyClass / B . ;,类 模 板,类模板的局部特化,或: template class MyClass / C . ; 或: template class MyClass / D . ; 于是可以: MyClass mif; /使用A MyClass mff; /使用B MyClass mfi; /使用C MyClass mi; /错 MyClass mpp; /错,类 模 板,这后两句错都是二义性错: MyClass mi; 可以同等程度匹配 MyClass 和MyClass ; MyClass mpp; 可以同等程度匹配 MyClass 和 MyClass ; 当你再提供一个局部特化: template class MyClass / E . ; 后,第二种错误就不存在了。,类 模 板,typename的含义很单纯:向编译器明确告知,其后跟着的是个类型名。看下例: template struct iterator_traits typedef typename I:value_type value_type; ; 又如: template typename iterator_traits:value_type fun(I ite) return *ite; ,关键字typename的含义,特别予以指明 I:value_type是个类型 ,以便于typedef 使用。,这一整行是函数fun()的返回类型。 typename用来指明其后是个类型名。,类模板可以含有静态成员。这就使类模板的每个模板类的所有对象都共享静态成员; 类模板定义时不会创建静态成员,即静态成员在具现前是不存在的。是由模板类创建、由模板类的对象使用的。 静态数据成员也一定要初始化。若是模参类型,则分别初始化。,类 模 板,类模板与静态,类模板与静态,类模 板,对象1,静态成员,对象n,模 板类,一级实例化,静态成员,对象1,对象n,模 板类,二级实例化,静态成员,#include template class Base static T num ; / T 类型的私有静态数据成员 public: static void show() /静态函数成员 cout:num = 10; /静态数据成员的初始化 char* Base:num = “Hello!“; /若静态数据成员是确定类型,则仅初始化一次。这意味着什么?,void main() Base obj1; obj1.show(); Base obj2; obj2.show(); Base:show(); Base :show(); 结果显示: 10 Hello! 10 Hello!,类模板可以含有友元(函数和类)。 1. 若友元函数是普通函数,则它将是这个类模板的所有模板类的友元函数; 2. 若友元函数是函数模板,但其模参与类模板的模参无关,则这个友元函数所有的模板函数都将是类模板的所有模板类的友元函数; 3. 若友元函数是函数模板,其模参与类模板的模参相关,则这个友元函数的模板函数有可能是该类模板的特例(不是所有的)类的友元;,类 模 板,类模板与友元,类模板中含有模板的友元运算符重载函数 #include #include #include #include class B /声明一个普通类 int member; public: B(int a=0) member=a; cout“B constructor is called ! “endl; B() cout“B destructor is called ! “endl; B(B ,template /声明一个模板类 class A B b; /使用了类组合 public: A(B

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论