一、模板进阶
非类型模板参数
在模板中,模板参数分为类型形参和非类型形参。
类型形参就是我们经常用的,出现在模板的参数列表中,跟在class或typename之后的参数类型名称。
而非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用
我们来看下面的代码:
在上图中,我们写了一个Array类,里面的成员变量是一个数组。可以看到,它的数组大小是用N来控制的,是一个静态数组。当我们需要改变这个数组的大小的时候,就必须要去改变宏中定义的值。但是,如果我们现在需要两个Array类构建的数组,一个容量是10,另一个容量是100,此时宏就无法满足我们的需求。为了解决这种情况,就有了“非类型模板参数”。
在上图中,"T"是类型形参,“N”就是非类型形参。可以看到,通过传非类型形参的方式,就可以控制开辟的数组容量。当然,我们也可以给N加上缺省值。
虽然非类型形参是用常量当参数,但还是有要求的。就是非类型形参只能传入整型。浮点型、类对象、字符串都是允许作为参数的。
同时,非类型的模板参数必须要在编译期流能确认结果。传入的值不能是一个变量:
在我们的库中,也存在一个array类,就使用了非类型参数。上面我们都是在类模板中使用。如果我们在函数模板中使用,就需要将函数模板显式实例化:
如果我们用隐式实例化,就会出现报错。所以如果在函数模板中使用了非类型参数,就必须要显式实例化:
模板特化
在通常情况下,使用模板可以实现一些与类型无关的代码。但对于一些特殊类型的可能会得到一些错误的结果,需要进行特殊处理。
2.1函数模板
例如在以下的代码中:
我们提前准备了一个日期类。在这个日期类中提供了“<”重载以进行比较。但是我们运行上面的程序可以看到,虽然第一个和第二个比较都是正确的,但是第三个的比较结果确实错误的。原因很简单,我们这里传入的是p1和p2,比较的是指针大小。我们可以通过解引用的方式解决。但是,如果在这里,我们不想解引用,就想传指针进行比较呢?这个时候就需要针对某些类型进行特殊处理。以上图为例,我们要特殊处理的类型就是“Date* ”。此时,就可以用“模板特化”。
2.1.1函数模板特化使用
模板特化的使用方法也很简单:1.要有一个基础的函数模板;2.关键字template后面接一对空的<>;3.函数名后跟一对<>,尖括号中为需要特化的类型;4.函数形参表必须要和模板函数的基础参数类型完全相同,否则可能出现报错
模板特化的模板中,不存在参数。当我们遇到Date*时,我们不希望用left < right比较,而是用*left < *right比较。
当我们再次运行该程序时,其他类型传入时就会去匹配Less(T left, T right)。但是当我们传入Date*时,编译器就会在匹配类模板之前,去下面看有没有针对该类型的模板特化,如果有,就去匹配模板特化:
2.2类模板
如果我们有以下代码:
我们现在想根据传入的某些类型,如double类型,进行特殊化处理,也可以使用模板特化:
类模板的特化和函数模板差不多。都要在template后的接一个空的<>。不同的是,类模板特化的特化类型要用<>写在类名后,与原模板的参数一一对应;并且类模板特化中不需要写成员变量。
2.3特化类型
特化一共有两种类型,全特化和半特化(偏特化)。
2.3.1全特化
全特化,即模板参数全部都进行特化。如下图:
该图中的Data类的两个参数都进行了特化,这就是全特化。
2.3.2半特化(偏特化)
半特化,也叫做偏特化。意思是可以仅对部分参数进行特化:
上图中就是一个半特化。我们只特化了第二个参数。在这种情况下,函数进入类模板调用函数之前,会下去下面找有没有针对对应参数的模板特化,有则调用对应的模板特化。因此,这里d2调用的就是模板特化而非原模板。
半特化时,我们必须要在template后面的<>中,填入未被特化的参数。
如果全特化和半特化同时存在,它的匹配原则是:全特化 > 半特化 > 原模板。
半特化还有一种使用方式,用于参数类型的进一步限制: