【C++】模板及模板的特化
目录
一,模板
1,函数模板
什么是函数模板
函数模板原理
函数模板的实例化
推演(隐式)实例化
显示实例化
模板的参数的匹配原则
2,类模板
什么是类模板
类模板的实例化
二,模板的特化
1,类模板的特化
全特化
偏特化
2,函数模板的特化——全特化
三,非类型模板参数
一,模板
1,函数模板
什么是函数模板
所谓函数模板,实际上是建立一个通用的函数,该函数类型和形参类型不具体指定,而是用一个表示任意类型的虚拟类型来代表(这里的任意类型可以任意选择,如 T)。
定义函数模板的一般形式:
template
返回值类型 函数名(参数列表)
{
// .....
}
其中template和class是关键字,typename 可以用class关键字代替,在这里typename 和class没区别,括号中的参数叫模板形参,模板形参和函数形参很相像,模板形参不能为空。一但声明了模板函数就可以用模板函数的形参名声明类中的成员变量和成员函数,即可以在该函数中使用内置类型的地方都可以使用模板形参名。模板形参需要调用该模板函数时提供的模板实参来初始化模板形参,一旦编译器确定了实际的模板实参类型就称他实例化了函数模板的一个实例。比如swap的模板函数形式为
下面来看一下,完成多个不同类型的两个数据的交换
针对具体类型(常规函数)
// 完成两个整形变量的交换 void Swap(int& left, int& right) { int temp = left; left = right; right = temp; } // 完成两个double型变量的交换 void Swap(double& left, double& right) { double temp = left; left = right; right = temp; } // 完成两个字符型变量的交换 void Swap(char& left, char& right) { char temp = left; left = right; right = temp; } int main() { int a = 1, b = 2; double c = 1.1, d = 2.2; char ch1 = 'a', ch2 = 'b'; Swap(a, b); Swap(c, d); Swap(ch1, ch2); return 0; }
通过上面可以看出,若想要完成以上三种不同类型的交换须要写三个交换函数,这是针对每种类型分别写出具体的交换函数;但是以上的三种交换函数除了参数的类型不一样,其他都是一样的,显得代码既冗余又不够简练。来看下面使用函数模板
跟具体类型无关(函数模板)
//函数模板 //template template // 声明一个类型模板参数 T> void Swap(T& left, T& right) // 使用模板参数T来声明函数参数left和right { T temp = left; left = right; right = temp; } int main() { int a = 1, b = 2; double c = 1.1, d = 2.2; char ch1 = 'a', ch2 = 'b'; Swap(a, b); Swap(c, d); Swap(ch1, ch2); return 0; }
在这个例子中,T 是一个类型模板参数,它告诉编译器我们希望这个函数能够处理多种类型。在函数模板的声明中,使用 typename 关键字(也可以使用 class 关键字,两者在函数模板中都是等价的)来声明类型模板参数。
然后,使用类型模板参数 T 来声明函数的参数 left和 right,它们都是类型为 T 的引用。这意味着可以传递任何类型的变量给 swap 函数,只要这两个变量的类型相同。
函数模板原理
当编译器遇到一个函数调用时,编译器会尝试根据传递给函数模板的实参类型来推导出模板参数的类型,一旦类型推导成功,编译器就会生成一个或多个具体的函数实例,这些实例的类型与推导出的模板参数类型相匹配。
注意:函数模板本身并不产生代码,它只是一个蓝图。只有在模板被实例化时,编译器才会生成具体的函数代码。
当实参a、b 是 int 时,编译器会把模板参数 T 推演成 int 类型,会实例化出一份具体类型的Swap 函数来调用;当实参a、b 是 double 时, 编译器会把模板参数 T 推演成 int 类型; char类型也一样。
注意:以上三个函数在实例化时虽然走的都是同一个函数模板,但是调用的不是同一个函数;只是用同一个函数模板实例化出了三份针对具体类型的函数
如下:
函数模板的实例化
推演(隐式)实例化
隐式实例化:编译器在调用一个模板函数时,根据提供的参数类型自动推断出模板参数的类型,并生成相应的函数实例。这个过程是编译器自动完成的,不需要程序员显式指定模板参数的类型。
// 声明一个函数模板 template T Add(T a, T b) { return a + b; } int main() { int a1 = 3, a2 = 5; double d1 = 3.5, d2 = 6.5; // 隐式实例化:编译器根据参数类型(int)推断出模板参数T为int Add(a1, a2); // 生成了 add(int, int) 的实例 // 编译器根据参数类型(double)推断出模板参数T为double Add(d1, d2); // 生成了 add(double, double) 的实例 Add(a1, d1); // a1和d1是不同的类型,此时编译报错,编译器无法推演出具体的类型 return 0; }
上述第三个 Add(a1, d1); 该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型 通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T, 编译器无法确定此处到底该将T确定为int 或者 double类型而报错
解决方法:1. 可以改成多参数的函数模板 如:template
2. 手动强制类型转换 如:上面的 Add(a1, (int)d1); 或 Add((double)a1, d1);
3. 就是下面要说的 显式实例化
显示实例化
函数模板允许编写通用的函数,这些函数可以处理不同类型的数据。但是,在某些情况下,可能想要为特定的类型显式地实例化函数模板,以便在编译时生成具体的函数版本。
即:在函数名后的中指定模板参数的实际类型
// 声明一个函数模板 template T Add(T a, T b) { return a + b; } int main() { int a1 = 3, a2 = 5; double d1 = 3.5, d2 = 6.5; // Add(a1, d1); // a1和d1是不同的类型,此时编译报错,编译器无法推演出具体的类型 Add(a1, d1); // 显式实例化 可强制为 int 类型实例化,并将参数 d1 强制转换为 int 类型 // 或 Add(a1, d1); // 显式实例化 可强制为 double 类型实例化,并将参数 a1 强制转换为 double 类型 return 0; }
上面的模板与函数调用 Add(a1, d1) 不匹配,因为该模板要求两个函数参数的类型相同。但通过使用 Add(a1, d1), 可强制为 int 类型实例化,并将参数 d1 强制转换为 int 类型,这样就可以与函数 Add(int, int) 的第二个参数匹配。
模板的参数的匹配原则
1. 同时存在性:一个非模板函数可以和一个同名的函数模板同时存在。此外,这个函数模板还可以被实例化为这个非模板函数。
2. 优先调用非模板函数:当非模板函数和同名函数模板在参数上相匹配时,编译器会优先调用非模板函数,而不是从模板产生出一个实例(即有现成的就吃现成的)
3. 模板的更好匹配:如果模板可以产生一个具有更好匹配的函数,那么编译器会选择模板而不是非模板函数(即有更合适的就吃更合适的,没有就将就吃)。
// 函数模板,可以处理任何类型的加法 template T Add(T a, T b) { return a + b; } // 非模板函数,专门处理int类型的加法 int Add(int a, int b) { return a + b; } int main() { int a1 = 3, a2 = 5; double d1 = 3.5, d2 = 6.5; // 调用非模板函数,因为参数是int类型,与非模板函数匹配 Add(a1, a2); Add(d1, d2); // 调用模板函数,因为参数是double类型,与非模板函数不匹配 Add(a1, d1); // 调用模板函数,模板函数会生成更匹配的,因为参数是double类型,与非模板函数不匹配 (a1会被强转成 double) return 0; }
2,类模板
什么是类模板
类模板是对一批成员数据类型不同的类的抽象。只需为这一批类所组成的整个类家族创建一个类模板,并给出一套程序代码,就可以用来生成多种具体的类(这类可以看作是类模板的实例),从而大大提高编程的效率。
类模板的基本结构如下:
//template // 可以有多个类型参数,使用逗号分隔
template // 或使用typename代替class
class 类名
{
// 类的定义,可以使用类型T作为成员变量、函数参数或返回类型
};
其中,template 关键字用于声明一个模板, 部分定义了模板参数,这些参数在模板内部可以用作类型。class关键字是用来指示随后的标识符是一个类型名称的,但也可以使用 typename 关键字代替 class。
栈模板类可以定义如下:
// 类模板 template class Stack { public: Stack(int capacity = 4) { cout