模板
使用模板的目的就是能够让程序员编写与类型无关的代码。比如编写了一个交换两个整型 类型的 函数,这个函数就只能实现 型,对 ,字符这些类型无法实现,要实现这些类型的交换就要重新编写另一个 函数。
使用模板的目的就是要让这程序的实现与类型无关,比如一个模板函数,即可以实现 型,又可以实现 型
的交换。模板可以应用于函数和类。下面分别介绍
注意:模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在函数中声明或定义一个模板。
一、模板函数通式
模板函数的通用形式为: 形参名, 形参名 反回类型 函数名 参数列表 函数体 。其中和是关键字, 可以用 关键字代替,在这里 和 没区别, 括号中的参数叫模板形参,模板形参和函数形参很相像,模板形参不能为空。一但声明了模板函数就可以用模板函数的形参名声明类中的成员变量和成员函数,即可以在该函数中使用内置类型的地方都可以使用模板形参名。模板形参需要调用该模板函数时提供的模板实参来初始化模板形参,一旦编译器确定了实际的模板实参类型就称他实例化了函数模板的一个实例。比如 的模板函数形式为 ,当调用这样的模板函数时类型 就会被被调用时的类型所代替,比如 其中 和 是 型,这时模板函数 中的形参 就会被 所代替,模板函数就变为 。而当 其中 和 是 类型时,模板函数会被替换为,这样就实现了函数的实现与类型无关的代码。
注意:对于函数模板而言不存在 这样的调用,不能在函数调用的参数中指定模板形参的类型,对函数模板的调用应使用实参推演来进行,即只能进行 这样的调用,或者 。
二、类模板通式
1. 类模板的通用形式为: 形参名, 形参名… 类名 ,类模板和函数模板都是以开始后接模板形参列表组成,模板形参不能为空,一但声明了类模板就可以用类模板的形参名声明类中的成员变量和成员函数,即可以在类中使用内置类型的地方都可以使用模板形参名来声明。比如在类 中声明了两个类型为 的成员变量 和 ,还声明了一个反回类型为 带两个类型
为 的函数 。
2. 类模板对象的创建:比有一个模板类 ,则使用类模板创建对象的方法为A<int> m;
在类 后面跟上一个 尖括号并在里面填上相应的类型,这样的话类 中凡是用到模板形参的地方都会被 所代替。当类模板有两个模板形参时创建对象的方法为 类型之间用逗号隔开。
3. 对于类模板,模板形参的类型必须在类名后的尖括号中明确指定。比如 用这种方法把模板形参设置为是错误的,类模板形参不存在实参推演的问题。也就是说不能把整型值 推演为 型传递给模板形参。要把类模板形参调置为 型必须这样指定 。
4. 在类模板外部定义成员函数的方法为:template<模板形参列表> 函数反回类型 类名<模板形参名>::函数名(参数列表){函数体},比如有两个模板形参 T1,T2 的类 A 中含有一个 void h()函数,则定义该函数的语法为:template<classT1,class T2> void A<T1,T2>::h(){}。注意当在类外面定义类的成员时 template 后面的模板形参应与要定义的类的模
板形参一致。
5. 再次提醒注意:模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在 main 函数中声明或定义一个模板。
三、模板的形数
有三种类型的模板形参:类型形参,非类型形参和模板形参。
1、 类型形参
1.1
类型模板形参:类型形参由关见字 class 或 typename 后接说明符构成,如 template<class T> void h(T a){};其中 T 就是一个类型形参,类型形参的名字由用户自已确定。模板形参表示的是一个未知的类型。模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型说明符或类类型说明符的使用方式完全相同,即可以用于指定反回类型,变量声明等。
1.2 不能为同一个模板类型形参指定两种不同的类型,比如 template<class T>void h(T a, T b){},语句调用 h(2, 3.2)将出错,因为该语句给同一模板形参 T 指定了两种类型,第一个实参 2 把模板形参 T 指定为 int,而第二个实参 3.2 把模板形参指定为 double,两种类型的形参不一致,会出错。
2、 非类型形参
2.1 非类型模板形参:模板的非类型形参也就是内置类型形参,如 template<class T, int a> class B{};其中 int a 就是非类型的模板形参。
2.2 非类型形参在模板定义的内部是常量值,也就是说非类型形参在模板的内部是常量。
2.3 非模板类型的形参只能是 整型,指针和引用,像 double,String, String **这样的类型是不允许的。但是 double &,double*,对象的引用或指针是正确的。
2.4 调用非类型模板形参的实参 必须是一个常量表达式,即他必须能在编译时计算出结果。
2.5 注意:任何局部对象,局部变量,局部对象的地址,局部变量的地 址都不是一个常量表达式,都不能用作非类型模
板形参的实参。全局指针类型,全局变量,全局对象也不是一个常量表达式,不能用作非类型模板形参的实参。
2.6 全局变量的地 址或引用,全局对象的地址或引用const 类型变量是常量表 达式,可以用作非类型模板形参的实参。
2.7 sizeof 表达式的结果是一个常量表达式,也能用作非类型模板形参的实参。
2.8 当模板的形参是 整型时调用该模板时的实参必须是整型的,且在编译期间是常量,比如 template <class T, int a> classA{};如果有 int b,这时 A<int, b> m;将出错,因为 b 不是常量,如果 const int b,这时 A<int, b> m;就是正确的,因为这时 b 是常量。
2.9 非类型形参一般不应用于函数模板中,比如有函数模板 template<class T, int a> void h(T b){},若使用 h(2)调用会出 现无法为非类型形参a 推演出参数的错误,对这种模板函数可以用显示模板实参来解决,如用 h<int, 3>(2)这样就把非类型形参a 设置为整数 3。显示模板实参在后面介绍。
2.9 非类型模板形参的形参和实参间 所允许的转换
1、允许从数组到指针,从函数到指针的转换。如:template <int *a> class A{}; int b[1]; A<b> m;即数组到指针的转换
2、const 修饰符的转换。如:template<const int *a> class A{}; int b; A<&b> m; 即从 int *到 const int *的转换。
3、提升转换。如:template<int a> class A{}; const short b=2; A<b> m; 即从 short 到 int 的提升转换
4、整值转换。如:template<unsigned int a> class A{}; A<3> m; 即从 int 到 unsigned int 的转换。
5、常规转换。
四、类模板的默认模板类型形参
1、 可以为类模板的类型形参提 供默认值,但不能为函数模板的类型形参提供默认值。函数模板和类模板 都可以为模板的非类型形参提 供默认值。
2、 类模板的类型形参 默认值形式为:template<class T1, class T2=int> class A{};为第二个模板类型形参 T2 提供 int 型的默认值
3、 类模板类型形参 默认值和函数的默认参数一样,如果有多个类型形参则从第一个形参设定了默认值之后的所有模板形参都要设定默认值,比如 template<class T1=int, class T2>class A{};就是错误的,因为 T1 给出了默认值,而 T2 没有设定。
4、 在类模板的外部定义类中的成员时 template 后的形参表应省略默认的形参类型。比如 template<class T1, classT2=int>class A{public: void h();}; 定义方法为 template<class T1,class T2> void A<T1,T2>::h(){}。
五、模板的实例化,区别声明,定义,实例化的概恋
1、声明就是 让编译器知道有这么一个函数或者类,比如 void h(); class A;就声明了一个 无参函数h 和一个类 A。定义就是对函数或者类的实现。比如 void h(){},classA{int a;}; 就定义了一个 什么也不做的函数h 和有一个变量a 的类 A。
2、声明和定义 都不会创建实例,只有在使用该函数或 者类的时候才会创建一个实例,比如在 main 函数中调用函数 h,创建类 A的对象时都会创建函数h 和类 A的实例,如果函数h 是外部函数则如 果在main 函数中没有调用函数h,就不会创建函数h 的实例。
3、模板的声明,定义,实例化:声明一个模板的形式为 template<class T> void h(T a); 或 template<class T> class A;这就声明了一个模板函数 h 和一个模板类 A。注意后面有个 分号。模板的定义和函数或类的定义相同。模板的实例化发生在调用该模板函数和模板类时,比如h(2)或A<int> m;就创建了一个int 型的h函数实例和类A的int实例版本。
当模板被实例化之后就会创建该模板的一个实例,在下次调用到相同的模板实例时就不会生成新的实例,而会调用以前创建的那个实例。比如有模板函数 template<class T> void h(T a){},则有调用 h(2)会生成一个int 型的模板函数实例,当第二次调用如 h(44)时会使用以 前生成的int 型模板函数实 例而不会创建新的实例。
4、当我们只是声明一个类模板的指针或引用时就没必要知道类模板的定义,也就是说 创建类模板的指针或引用时不会创建类模板的实例,只有指针被解引用或者该问类中的成员时才需要知道类模板的定义,才会实例化该类模板。比如 classA ; A *m; A &n;都不需要知道该类的定义,也不会实 例化该类。但是 m->a 或 n->a 时就会需要知道该类的定义了,因为这里指针访问类中的成员,须要类的定义,这时也要创建一个类的实例。
六、模板类型形参与实参间所允许的转换或模板实参推演
1、 模板实参推演:当函数模板 被调用时,对函数实参类型的检查决定了模板实参的类型和值这个过程叫做模板实参推演。比如 template<class T> void h(T a){};h(1);h(2.2),第一个调用因为实参 1 是 int 型的,所以模板形参T被推演为int 型,因此函数体中的所有 T 被替换为 int。而第二个调用中 double 类型的实数 3.2 决定了 T 的类型为 double。
2、在模板被实例化后就会生成一个新的实例,但这个新生成的实例不存在类型转换。比如有函数模板 template<class T>void h(T a){}。int a=2; short b=3; 第一次调用 h(a)生成一个 int 型的实例版本,但是当用 h(b)调用时不会使用 上次生成的 int 实例把short 转换为 int,而是会另外生成一个新的short 型的实例。
3、在模板实参 推演的过程中有时类型并不会完全匹配,这时编译器允许以下几种实参到模板形参的转换,这些转换不会生成新的实例。
3.1、数组到指针的转换或函数到指针的转换:比如 template<class T> void h(T * a){},int b[3]={1,2,3};h(b);这时数组b 和类型 T *不是完全匹配,但允许从数组到指针的转换因此数组 b 被转换成 int *,而类型形参 T 被转换成 int,也就是说函数体中的 T 被替换成 int。
3.2、限制修饰符转换:即把 const 或 volatile 限定符加到指针上。比如 template<class T> void h(const T* a){},int b=3;h(&b);虽然实参&b 与形参 const T*不完全匹配,但因为允许限制修饰符的转换,结果就把&b 转换成 const int *。
而类形型参 T 被转换成 int。如果模板形参是非const 类型,则无论实参是const 类型还是非const 类型调用都不会产生新的实例。
3.3、到一个基类的转换(该基类根据一个类模板实例化而来):比如 tessmplate<class T1>class A{}; template<class T1>class B:public A<T1>{}; template<class T2> void h(A<T2>& m){},在 main 函数中有 B<int> n; h(n);函数调用的 子类对象n 与函数的形参 A<T2>不完全匹配,但允许到一个基类的转换。在这里转换的顺序为,首先把子类对象n 转换为基类对象 A<int>,然后再用 A<int>去匹配函数的形参 A<T2>&,所以最后 T2 被转换为 int,也就是说函数体中的 T 将被替换为int。
4、再次提醒:对于函数模板而言不存在 h(int,int)这样的调用,不能在函数调用的参数中指定模板形参的类型,对函数模板的调用应使用实参 推演来进行,即只能进行 h(2,3)这样的调用,或者int a, b; h(a,b)。
七、显示实例化,显示模板实参,显示具体化,模板特化,模板函数重载
7.1函数模板的显示实例化
1、隐式实例化:比如有模板函数 template<class T> void h(T a){}。h(2)这时 h 函数的调用就是 隐式实例化,既参数 T 的类
型是隐式确定的。
2、函数模板显示实例化声明:其语法是:template 函数反回类型 函数名<实例化的类型> (函数形参表); 注意这是声明
语句,要以分号结束。例如:template void h<int> (int a);这样就创建了一个 h 函数的 int 实例。再如有模板函数
template<class T> T h( T a){},注意这里h 函数的反回类型为 T,显示实例化的方法为 template int h<int>(int a); 把 h 模
板函数实例化为int 型。
3、类模板的显示实例化:和函数模板的显示实例化一样都是以template 开始。比如 template class A<int,int>;将类 A 显示
实例化为两个int 型的类模板。这里要注意显示实例化后面不能有对象名,且以分号结束。
4、显示实例化可以让程序员控制模板实例化发生的时间。
5、对于给定的函数模板实例,显示实例化声明在一个文件中只能出现一次。
6、在显示实例化声明所在的文件中,函数模板的定义 必须给出,如果定义不可见,就会发生错误。
7、注意:不能在局部范围类显示实例化模板,实例化模板应放在全局范围内,即不能在 main 函数等局部范围中实例化
模板。因为模板的声明或定义不能在局部范围或函数内进行。
7.2显示模板实参
1、显示模板实参:适用于函数模板,即在调用函数时 显示指定要调用的时参的类型。
2、格式:显示模板实参的格式为在调用模板函数的时候在函数名后用<>尖括号括住要显示表示的类型,比如有模板函数
template<class T> void h(T a, T b){}。则 h<double>(2, 3.2)就把模板形参 T 显示实例化为 double 类型。
3、显示模板实参用于同一个模板形参的类型不一致的情况。比如 template<class T> void h(T a, T b){},则 h(2, 3.2)的调用会出错,因为两个实参类型不一致,第一个为 int 型,第二个为 double 型。而用 h<double>(2, 3.2)就是正确的,虽然两个模板形参的类型不一致 但这里把模板形参显示实例化为double 类型,这样的话就允许进行标准的隐式类型转换,
即这里把第一个int 参数转换为double 类型的参数。
4、显示模板实参用法二:用于函数模板的反回类型中。例如有模板函数 template<class T1, class T2, class T3> T1 h(T2 a, T3b){},则语句 int a=h(2,3)或 h(2,4)就会出现模板形参T1 无法推导的情况。而语句 int h(2,3)也会出错。用显示模板实参就参轻松解决这个问题,比如 h<int, int, int>(2,3)即把模板形参 T1 实例化为int 型,T2 和 T3 也实例化为int 型。
5、显示模板实参用法三:应用于模板函数的参数中 没有出现模板形参的情况。比如 template<class T>void h(){}如果在main 函数中直接调用h 函数如 h()就会出现无法推演类型形参T 的类型的错 误,这时用显示模板实参就不会出现这种错误,调用方法为 h<int>(),把 h 函数的模板形参实 例化为int 型,从而避免这种错误。
6、显示模板实参用法四:用于函数模板的非类型形参。比如 template<class T,int a> void h(T b){},而调用 h(3)将出错,因为这个调用无法为非类型形参推演出正确的参数。这时正确调用这个函数模板的方法为 h<int, 3>(4),首先把函数模板的类型形参 T 推演为 int 型,然后把函数模板的非类型形参 int a 用数值3 来推演,把变量 a 设置为 3,然后再把 4 传
递给函数的形参 b,把 b 设置为 4。注意,因为 int a 是非类型形参,所以调用非类型形参的实参应是编译时常量表达式,不然就会出错。
6、在使用显示模板实参时,我们只能省略掉尾部的实参。比如 template<class T1, class T2, class T3> T1 h(T2 a, T3 b){}在显示实例化时 h<int>(3, 3.4)省略了最后两个模板实参T2 和 T3,T2 和 T3 由调用时的实参 3 和 3.4 隐式确定为 int 型和double 型,而 T1 被显示确定为 int 型。h<int, , double><2,3.4>是错误的,只能省略尾部的实参。
7、显示模板实参最好用在存在二义性或模板实参推演不能进行的情况下。
7.3显示具体化(模板特化,模板说明和函数模板的重载
1、 具体化或特化或模板说明指的是一个意思,就是把模板 特殊化,比如有模板 template<class T>void h(T a){},这个模板适用于所有类型,但是有些特殊类型不需要与这个模板相同的操作或者定义,比如 int 型的 h 实现的功能和这个模板的功能不一样,这样的话我们就要重定义一个 h 模板函数的 int 版本,即特化版本。
特化函数模板:
2、 显示特化格式为:template<> 反回类型 函数名<要特化的类型>(参数列表) {函数体},显示特化以 template<>开头,表明要显示特化一个模板,在函数名后<>用尖括号括住要特化的类型版本。比如 template <class T> void h(T a){},其int 类型的特化版本为template<> void h<int>(int a){},当出现int 类型的调用时就会调用 这个特化版本,而不会调用通用的模板,比如 h(2),就会调用 int 类型的特化版本。
3、 如果可以从实参中推演出模板的形参,则可以省略掉显示模板实参的部分。比如:template<> void h(int a){}。注意函数 h 后面没有<>符号,即显示模板实参部分。
4、 对于反回类型为模板形参时,调用该函数的 特化版本必须要用显示模板实参调用,如果不这样的话就会出现其中一个形参无法推演的情况。如 template<class T1,class T2,class T3> T1 h(T2 a,T3 b){},有几种特化情况:
情况一:template<> int h<int,int>(int a, in b){}该情况下把T1,T2,T3 的类型推演为int 型。在主函数中的调用方式
应为 h<int>(2,3)。
情况二:template<> int h(int a, int b){},这里把 T2,T2 推演为 int 型,而 T1 为 int 型,但在调用时必须用显示模板实参调用,且在<>尖括号内必须指定为 int 型,不然就会调用到通用函数模板,如 h<int>(2,3)就会调用函数模板的 特化版本,而 h(2,3)调用会出错。h<double>(2,3)调用则会调用 到通用的函数模板版本。
这几种情况的特化版本是错误的,如 template<> T1 h(int a,int b){},这种情况下 T1 会成为不能识别的名字,因而出现错误,template<> int h<double>(int a,int b){}在这种情况下反回类型为int 型,把 T1 确定为 int 而尖括号内又把T1确定为 double 型,这样就出现了冲突。
5、具有相同名字和相同数量反回类型的非模板函数(即普通函数),也是函数模板 特化的一种情况,这种情况将在后面参数匹配问题时讲解。
函数模板重载
1、 函数模板可以重载,注意类模板不存在重载问题,也就是说出现这两条语句时 template<class T>class A{};
template<class T1,class T2>class A{};将出错。
2、 模板函数重载的形式为:template<class T> void h(T a, int b){}。Template<class T>void h(T a, double b){}等。
3、 重载模板函数要注意二义 性问题,比如 template<class T> void h(T a, int b){}和 template<class T>void h(T a, T b){}这两
个版本就存在二义性问题,当出现语句h(2,3)时就不知道调用哪个才正确,在程序中应避免这种情况出现。
4、 重载函数模板的第二个二义性问题是 template<class T>void h(T a, T b){} 与 template<class T1, class T2>void h(T1 a,T2
b){},当出现h(2,4)这样的调用时就会出现二义性。解决这个问题的方法是使用显示模板实参,比如要调用第一个 h函数,可以使用语法 h<int>(2,3),调用第二个 h 函数的方法为 h<int, int>(2,3)。
5、 函数模板的 特化也可以理解为函数模板重载的一种形式。只是特化以template<>开始。
6、 重载的特殊情况:比如 template<class T1,class T2> void h(T1 a, T2 b){},还有个版本如 template<class T1>void h(T1 a,int b){}这里两个函数具有两同的名字和相同的形参数量,但形参的类型不同,可以认为第二个版本是第一个版本的重载版本。
7、 函数模板的 重载和特化很容易混晓,因为特化很像是一个函数的重载版本,只是开头以template<>开始而已。
特化类模板:
6、 特化整个类模板:比如有 template<class T1,class T2> class A{};其特化形式为 template<> class A<int, int>{};特化形式以 template<>开始,这和模板函数的形式相同,在类名 A 后跟上要特化的类型。
7、 在类特化的外部定义成员的方法:比如 template<class T> class A{public: void h();};类 A 特化为 template<> classA<int>{public: void h();};在类外定义 特化的类的成员函数h 的方法为:void A<int>::h(){}。在外部定义类 特化的成员时应省略掉template<>。
8、 类的特化版本应与类模板版本有相同的成员定义,如果不相同的话那么当类特化的对象访问到类模板的成员时就会出错。因为当调用类的 特化版本创建实例时创建的是特化版本的实例,不会创建类模板的实例,特化版本如果和类的模板版本的成员不一样就有可能出现这种错误。比如:模板类 A 中有成员函数 h()和 f(),而特化的类A 中没有定义成员函数 f(),这时如果有一个特化的类的对象访问到模板类中的函数 f()时就会出错,因为在特化类的实例中找不到这个成员。
9、类模板的部 分特化:比如有类模板 template<class T1, class T2> class A{};则部分特化的格式为template<class T1> classA<T1, int>{};将模板形参 T2 特化为 int 型,T1 保持不变。部分特化以 template 开始,在<>中的模板形参是不用 特化的模板形参,在类名 A 后面跟上要特化的类型。如果要特化第一个模板形参T1,则格式为template<class T2> classA<int, T2>{};部分特化的另一用法是 template<class T1> class A<T1,T1>{};将模板形参 T2 也特化为模板形参 T1 的类型。
10、在类部分特化的外面定义类成员的方法:比如有部 分特化类template<class T1> class A<T1,int>{public: void h();};则在类外定义的形式为 template<class T1> voidA<T1,int>::h(){}。注意当在类外面定义类的成员时 template 后面的模板形参应与要定义的类的模板形参一 样,这里就与部分特化的类 A 的一样template<class T1>。
其他说明:
11、可以对模板的特化 版本只进行声明,而不定义。比如 template<> void h<int>(int a);注意,声明时后面有个分号。
12、在调用模板实例之前必须要先对特化的模板进行声明或定义。一个程序不允许同一模板实参集的同一模板既有显示特化又有实例化。比如有模板 template<class T> void h(T a){}在 h(2)之前没有声明该模板的 int 型特化版本,而是在调用该模板后定义该模板的 int 型特化版本,这时程序不会调用该模板的特化版本,而是调用该模板 产生一个新的实
例。这里就有一个问题,到底是调用由 h(2)产生的实例版本呢还是调用程序中的特化版本。
13、注意:因为模板的声明或定义不能在局部范围或函数内进行。所以特化类模板或函数模板 都应在全局范围内进行。
14、在特化版本中模板的类型形参是不可见的。比如 template<> void h<int,int>(int a,int b){T1 a;}就会出现错误,在这里模板的类型形参 T1 在函数模板的 特化版本中是不可见的,所以在这里 T1 是未知的标识符,是错误的。
八、匹配问题即函数的重载解析问题
1、当模板函数即有 重载版本又有非模板函数的重载版本时就会出现参数的匹配问题,模板函数 最后会选择最佳匹配的函数调用。过程为先进行参数匹配,参后从匹配好的函数列表中选择完全匹配的函数,最后再在完全匹配的函数中选择最佳匹配的函数。
2、参数的匹配过程如下。
a、创建候选函数列表。其中包含与被调用函数的名称相同的函数和模板函数(其中模板函数推演成功才会加入到候选函数列表中)。
b、使用候选函数列表创建可行函数列表。这些都是参数数目正确的函数,为此有一个隐式转换序列,其中包括实参类型与相应的形参类型完全 匹配的情况。
c、确定是否有最佳的可行函数。如果有则使用之,否则出错。
3、确定最佳可行函数的步骤,首先应确定哪些是完全匹配的,完全匹配从最佳到最差的顺序为
a、完全匹配,但普通函数优于模板函数及模板函数的特化版本
b、提升转换(如:char 和 short 转换为 int,及 float 转换为 double)
c、标准转换(如:int 转换为 char,及 long 转换为 double)
d、用户定义的 转换,如类声明中定义的 转换。
注意,完全匹配允许一些无关紧要的转换,这些无关紧要的转换如下。其中包括
const type到const type&的转换
形参 实参 形参 实参 形参 实参 形参 实参
type & type type type & type * type[] type(参数列表) type(*)(参数列表)
const type type volatile type type const type * type * volatile type* type *
比如:有函数 void h(int a), void h(int &a),其中 int b=3;则调用 h(b)将出错。因为两个函数 都是完全匹配且是最佳匹配的。
完全匹配示例:比如有如下函数定义:
#1 void may(int a); #2 float may(float a, float b=3); #3 void may(char a); #4 char *may(const char* a);则如调用 ‘ ’ 对这个调用 和 不可行,因为整数不能被隐式转换为指针类型。调用优先于 ,因为 到 是提升转换,而 到 是标准转换,提升转换优先于标准转换,所以 优先于
。都优先于 和 ,因为 都是完全匹配,其中 和 优先于 ,因为 是模板函数。其中 和 都是完全匹配,且是最佳的,这样就出现了二义性问题的错误。
注意 是 类型的形参,在这里应用了完全匹配的无关紧要的转换, 形参到 实参的转换和
形参到 实参的转换。
4、但是完全匹配并不是最佳匹配,最佳匹配的原则如下、如果多个函数都是完全匹配,则非 数据的指针或引用形参优先于 的指针和引用的参数匹配。这条规则只适合于指针或引用参数,当参数是非指 针或引用时将出现二义性。比如 和 其中当有调用 时, 优先于 因为在这里变量 没有被声明为 ,而第一个函数 又是非指针函数,所以第一个函数优先于第二个函数。但对于 这样的调用,则第二个函数将 优先于第一个函数的调用,因为这里数值 本身就是 常量,所以第二个函数更优先。而 和 当出现 时就会出现二义性问题。
九、类模板中的模板成员
(模板函数,模板类)和静态成员
1、 类模板中的模板函数和模板类的声明:与普通模板的声明方式相同,即都是以 template 开始
2、 在类模板外定义类模板中的模板成员的方法:比如 template<class T1> class A{public:template<class T2> class B;
template<class T3> void g(T3 a);};则在类模板外定义模板成员的方法为,template<class T1> template<class T2> class
A<T1>::B{};定义模板函数的方法为:template<class T1> template<class T3> void A<T1>::g(T3 a){}其中第一个 template
指明外围类的模板形参,第二个 template 指定模板成员的模板形参,而作用域解析运算符指明是来自哪个类的成员。
3、 实例化类模板的模板成员函数:比如上例中要实例化函数g()则方法为, A<int> m; m.g(2);这里外围类 A 的模板形参由
尖括号中指出,而类中的模板函数的参数由 整型值2 推演出为 int 型。
4、 创建类模板中的模板成员类的对象的方法:比如上例中要创建模板成员类 B 的方法为,A<int>::B<int> m1;
A<int>::B<doble>m2; A<double>::B<int> m3;在类模板成员 B 的前面要使用作用域解析运算符以指定来自哪个外围类,并且在尖括号中要指定创建哪个外围类的实例的对象。这里说明在类模板中定义模板类成员时就意味意该外围模板类的一个实 例比如int 实例将包含有多个模板成员类的实例。比如这里类A 的 int 实例就有两个模板成员类B 的int 和 double 两个实例版本。
5、 要访问类模板中的模板成员类的成员遵守嵌套类的规则,因为类模板中的模板成员类就是一个 嵌套类。即外围类和嵌套类中的成员是相 互独立的,要访问其中的成员只能通过嵌套类的指针,引用或对象的方式来访问。具体情况见嵌套类部分。
6、 类模板中的 静态成员是类模板的所有实例所共享的。
十、友元和类模板
1、类模板中有 普通友元函数,友元类,模板友元函数和友元类,普通友元函数和友元类不做介绍。
2、可以建立两种类模板的友元模板,即约束型的友元模板和非约束型的友元模板。
3、非约束型友元模板:即类模板的 友元模板类或者友元模板函数的任一实例都是外围类的任一实例的友元,也就是外围类和友元模板类或友元模板函数之间是多对多的关系
4、约束型友元模板:即类模板的 友元模板类或友元模板函数的一个特定实例只是外围类的相关的一个实例的友元。即外围类和友元模板类或友元模板函数之间是一对一的关系。
3、约束型友元模板函数或友元类的建立:比如有前向声明:template<class T1> void g(T1 a); template<class T2> void g1();
template<class T3>class B;则 template<class T>class A{friend void g<>(T a); friend void g1<T>(); friend class B<T>;};就建立了三个约束型友元模板,其中 g 和 g1 是函数,而 B 是类。注意其中的语法。这里 g<int>型和类 A<int>型是一对一的友元关系,g<double>和 A<double>是一个一对一的友元关系。
6、非约束型友元模板函数或友元类的建立:非约束型友元模板和外围类具有不同的模板形参,比如 template<class T>classA{template<class T1> friend void g(T1 a); template<class T2> friend class B;}注意其中的语法,非约束型友元模板都要以template 开头。要注意友元模板类,在类名 B 的后面没有尖括号。
7、不存在部分约束型的友元模板或者友元类:比如 template<class T> class A{template<class T1>friend void g(T1 a, T b);template<class T3>friend class B<T3,T>;}其中函数 g 具有 template<class T1,class T2>void g(T1 a,T2 b)的形式。其中的函数g 试图把第二个模板形参部分约束为类 A 的模板形参类型,但是这是无笑的,这种语法的结果是 g 函数的非 约束型类友元函数,而对类B 的友元声明则是一种语法错误。
十二、模板中的关见字
typename和class的区别
1、首先 typename 是一个较新的关见字,而 class 是比较老的关见字,在类模板的声明中使用 哪个都是一样的。
2、必须使用 typename 关见字的情况:当有一个模板,且模板的类型形参和某一个类名同名,而且这个同名的类中又有
一个嵌套类的时候就要使用关见字typename 了,比如有类class T{public: class A{};}; 则template<class T> class B{T::A m;};这时的语句就会出错,因为模板类 B 无法区别出 T::A 表示的是一个类型 还是一个数据成员,在 C++里面默认情况下是一个数据成员,在这里的语句T::A m就会出错,会出现m前缺少类型说明符的错误。要使该式正确就必须在
T::A 前加上 typename 关见字以说明 T::A 表示的是一个类型。即 template<class T> class B{typename T::A m;}这样就正确地声明了一个类 T 的嵌套类 A 的对象 m。当然如果模板的类型形参和那个类名不同名时不会出现这种错误,比如把嵌套类B 的外围类的类名 改为D,则在模板类 B 中的语句 D::B m;将是正确的语句。

浙公网安备 33010602011771号