Template
一个模板完全都是声明,应该只有
.h
,而不含有.cpp
当我们需要操作不同类型的,功能和实现类似的容器,往往就需要使用模板元编程。
其他的一些解决方案:
- 构造公共父类(Java 的 Object 类)
- 可能无法实现(C++ 没有单根结构,有 Fully in 结构)
- 复制代码
- 设计不良
- 无类型的容器
- 类型检查非常差
概念
模板实际上是代码的重用。
- 产生了泛型编程(generic programming)
- 在定义中将类型作为参数
分为函数模板和类模板。注意,模板不是类或者函数,它用于生成类或函数。
模板本身不会存在于编译后的代码中。
模板实例化指用模板和具体的类型生成具体的函数和类。
函数模板
模板声明
使用 template
关键字来声明模板,其中用 typename
或 class
声明类型参数,他们是等价的。
template<typename _Tp>
void swap(_Tp& x, _Tp& y){
_Tp temp = x;
x = y;
y = temp;
}
模板实例化
#include <algorithm>
#include <iostream>
int add(int x, int y){
return x + y;
}
template<typename _Tp>
_Tp add(_Tp x, _Tp y){
return x + y;
}
signed main(int argc, char **argv){
std::cout << add(1, 2) << std::endl;
std::cout << add(1.1, 2.2) << std::endl;
return 0;
}
编译,使用 nm -CU a.out
指令,可以发现定义了两个函数
T add(int, int)
和 T double add<double>(double, double)
。
一般情况下(全部
可以显式地实例化:add<double>(1.1, 2.2)
。在函数参数不能推断出模板中所有参数时,请使用显式实例化。
参数匹配
可以发现,如果有原生的完全匹配的函数,优先使用原生函数,例如 add(1, 2)
调用 add(int, int)
。
其次,如果有模板能完全匹配的函数,使用模板生成函数,例如 add(1.1, 2.2)
调用 add<double>(dobule, double)
。
再其次,尝试使用类型转换来匹配其他原生函数。但是,类型转换不能用于匹配模板,例如 add(1, 2.2)
。
类模板
模板声明
template<typename T>
class Vector{
public:
Vector(int s):size(s){
content = new T[size];
}
virtual ~Vector(){
delete[] content;
}
T& operator[](int p){
return content[p];
}
private:
T* content;
int size;
};
可以在模板参数中使用一些常量
template<typename T, int bounds = 100>
class Container{
public:
private:
int content[bounds];//ok
}
Container<int, 500> v1;
Container<int> v2;
这样的操作可以让代码稍微快一点(相比于动态内存分配),但会造成代码冗余(所有容器都需要指定长度、内部实现都要带参数),所以应当慎重使用。
模板实例化
Vector<int> vi(4);
Vector<std::string> vs(4);
可以嵌套实例化
std::vector<std::vector<int>> array;
std::vector<int (*)(std::vector<double>&, int)> funcptr;
继承
template<typename T>
class Derived: public Base;
template<typename T>
class Derived: public List<T>;
class Group: public List<Employee*>;
模板可以从实例类继承,可以从类模板继承。
实例类只能从实例类继承。
备注
总的来说,模板的内容应该全部放在头文件中,不能用单独的 .cpp
文件来保存实现。
这是因为,模板并不是函数或类,全部都是声明,编译后只有实例化的函数和类会留在 .obj
中。
静态变量
对于同一个模板类的静态变量,应该在 .h
中声明其位置:
template<typename T>
int Derived<T>::size = 10;
这样实际调用时,会为每个不同的 T
生成一个 size
变量。
使用建议
在编写一个模板时,可以注意以下要点:
- 先编写一个非模板的版本(实例)
- 准备一些测试数据,准备用于测试效率和正确性
- 通过观察,确定需要作为参数的类型
- 将类型转换为类型参数,并使用数据测试