注意:其他语言也有模版的特性,本文仅介绍c++。
1. 什么是c++的模版?
假设你接到一个需求,这个需求很简单:实现两个数字的相加,数字类型为整型。你该如何实现这个需求?
接到需求后,你一拍脑袋,太简单了,于是你的第一版代码实现了。
int add(int a, int b) {
return a + b;
}
满怀信心的你提交了代码,却被告知,这样的代码不具备复用性。如果此时需求要新增一个浮点型相加,你要怎么办?碰了灰的你,想了想,整型和浮点型代码逻辑完全相同,但是因为类型不同,却要实现两个不同的函数,如果这样的话,代码就要像下面这样
int add(int a, int b) {
return a + b;
}
float add(float a, float b) {
return a + b;
}
可以看到除了类型之外,其余代码完全相同,有没有什么好方法,能用一个函数就囊括两种不同类型的变量的代码逻辑呢?
或许c++的模版可以帮助你。
1.1 c++模版
正如上面的场景提到的,c++模版提供了将参数类型模版化(模版参数)的能力,即可以利用模版,用同一套代码实现不同类型的相同逻辑。
c++的模版化,可以针对不同的实体,比如类、函数、类型、变量等。
模版的基本语法如下
template <parameter-list>
其中parameter-list就是模版参数,模版参数有如下几种类型
## 类型模版参数,尖括号中使用class或者typename作为关键词,T代表某种类型。 template<class T> template<typename T>
这种模版参数将类型T作为一种模版,可以很好地解决本文前述的需求。此处可以通过class或者typename定义。为什么要规定使用两种方式来定义类型模版参数?此处有两个原因,第一:历史原因,在早期只能用class定义类型模版参数,但是class与c++中class关键字容易混淆,让人以为这里是一个自定义class类型,故使用typename实现更泛化性的表达。第二:在某些场景,比如模版嵌套类型中必须使用typename进行类型的声明。比如
template <typename T> class Container { public: using value_type = T; }; // 模板函数,使用嵌套依赖类型 template <typename C> void printValue(const C& container) { // 使用typename来告诉编译器C::value_type是一个类型 typename C::value_type value = typename C::value_type(); std::cout << "Default value: " << value << std::endl; }
在函数printValue中,若没有typename声明 C::value_type,那么编译器会认为其是一个静态成员变量或者函数,而非类型。
// 非类型模版参数,尖括号中使用类型 + 变量名的方式定义 template<int a>
非类型模版参数和我们普通函数中的参数类似,但是和普通函数不同的是,模版中的参数是在编译时确定,而普通函数中的参数,在运行确定。所以非类型模版的参数通常在模版元编程时非常有用。
// 模版模版参数(俄罗斯套娃) template<template<typename> typename T> // 以下定义了一个容器类,然后将容器通过模版模版参数的方式集成到一个Map模版类型,这样可以比较方便的替换Map中具体的类实现。这种方法在STL中比较常见。 template<typename T> class my_array {}; // 在Map的模版定义中,使用template<typename> typename C = my_array指定了一个默认的容器模版类。 template<typename K, typename V, template<typename> typename C = my_array> class Map { C<K> key; C<V> value; };
注意模版模版参数的声明和定义方式。
1.2 c++模版的使用注意事项
- 模版定义和实现问题:与普通的函数不同的是,通过模版定义的函数或者类,它只是一个代码蓝图,而非真正的c++代码。模版的定义只是给了编译器一种生成代码的规范,即编译器可以通过模版自动实现一些代码。这和普通函数编译过程不同,为了编译器在编译阶段能看到模版血肉而非脉络,模版的定义和实现必须放到同一个h文件中。
- 虚函数不能作为模版函数:虚函数是运行时通过不同类实例中的虚表实现的,而模版参数是在编译期需要确定的,两者有冲突。再深入一点理解,当代码中调用一个虚函数时,这个虚函数后续真正执行的函数,只有在运行期才能确定,所以当我们把一个虚函数定义为模版函数时,编译器无法在编译期确定要调用哪一个函数,那么也就更谈不上用什么参数去实例化模版。(这里有一个先后关系,即编译器要先确定用哪个模版函数进行实例化,然后根据具体的参数实例化这个模版函数。)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。