文章目录模版1. 泛型编程2. 函数模版2.1函数模版格式2.2 函数模版原理2.3 函数模版的实例化2.4 模版参数的匹配原则3. 类模版3.1 类模版的定义格式3.2类模版的实例化模版1. 泛型编程在C语言中要写交换函数时要针对不同的参数类型来写多个对应的函数void swap_int(int x,int y) { int tmpx; xy; ytmp; } void swap_double(double x,double y) { double tmpx; xy; ytmp; } //………这未免有点麻烦了在C中有我们可以使用函数重载但这也不过是将函数名统一还是比较麻烦有没有什么一劳永逸的办法呢这就要提到泛型编程了。泛型编程编写与类型无关的通用代码是代码复用的一种手段。模板是泛型编程的基础模版他像一个模具一样只要写一次完整的语法规则就能自己根据参数推导出匹配的函数模版分为两部分函数模版和类模版2. 函数模版2.1函数模版格式templatetypename T//也可以把typename改为class void swap(T x,T y) { T tmpx; xy; ytmp; }定义模板的是​template​​typename/class是用来声明类型形参的2.2 函数模版原理写好一份模版这相当于一份蓝图。这段代码不生成任何机器指令它只是生成函数的模具在编译期编译器发现调用进行实例化swap(2,3); //编译器推导出 Tint swap(2.1,3.3); //编译器推导出 Tdouble编译器发现调用时用int​、double​来替换T生成两份独立的函数编译器在内部生成对应的真实代码void swap(int x,int y) { int tmpx; xy; ytmp; } void swap(double x,double y) { double tmpx; xy; ytmp; }用了几种类型生成几个版本这就是模板的零成本抽象生成的代码跟手写的一样高效没有运行时开销。代价是编译时间变长、二进制体积变大因为每种类型一份。‍2.3 函数模版的实例化用不同类型的参数使用函数模板时称为函数模板的实例化。模板参数实例化分为隐式实例化和显式实例化。隐式实例化让编译器根据实参推导模版参数的实际类型templateclass T T Add(const T left, const T right) { return left right; } int main() { int a1 10, a2 20; double d1 10.0, d2 20.0; Add(a1, a2); Add(d1, d2); /*看看下面这个例子 Add(a1, d1);一个是int类型一个是double类型 该语句不能通过编译因为在编译期间当编译器看到该实例化时需要推演其实参类型 通过实参a1将T推演为int通过实参d1将T推演为double类型但模板参数列表中只有 一个T 编译器无法确定此处到底该将T确定为int 或者 double类型而报错 注意在模板中编译器一般不会进行类型转换操作因为一旦转化出问题编译器就需要背黑锅 */ // 此时有两种处理方式1. 用户自己来强制转化 2. 使用显式实例化 // 强制类型转换 Add(a1,(int)d1); return 0; }显式实例化在函数名后的​中指定模版参数的实际类型int main(void) { int a 10; double b 20.0; // 显式实例化 Addint(a, b); return 0; }2.4 模版参数的匹配原则一个非模板函数可以和一个同名的函数模板同时存在而且该函数模板还可以被实例化为这个非模板函数// 非模版函数 专门处理int的加法函数 int Add(int left, int right) { return left right; } // 通用加法模版函数 templateclass T T Add(T left, T right) { return left right; } void Test() { Add(1, 2); // 与非模板函数匹配编译器不需要实例化 Addint(1, 2); // 调用编译器实例化的Add版本 }对于非模板函数和同名函数模板如果其他条件都相同在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数那么将选择模板// 专门处理int的加法函数 int Add(int left, int right) { return left right; } // 通用加法函数 templateclass T1, class T2 T1 Add(T1 left, T2 right) { return left right; } void Test() { Add(1, 2); // 与非函数模板类型完全匹配不需要函数模板实例化 Add(1, 2.0);// 模板函数可以生成更加匹配的版本编译器根据实参生成更加匹配的Add函数 //结果是3精度被截 //显式实例化Addint,double(1,2.0); }模版函数不能自动转换类型但是普通函数能够自动转换类型3. 类模版3.1 类模版的定义格式templateclass T1, class T2, ..., class Tn //注意每一个参数都要有关键字class与typename可以混用 class 类模板名 { // 类内成员定义 };#includeiostream using namespace std; // 类模版 templatetypename T class Stack { public: Stack(size_t capacity 4) { _array new T[capacity]; _capacity capacity; _size 0; } void Push(const T data); private: T* _array; size_t _capacity; size_t _size; }; // 模版不建议声明和定义分别写到到两个文件会出现链接错误 templateclass T void StackT::Push(const T data) { _array[_size] data; _size; } int main() { Stackint st1; // int Stackdouble st2; // double return 0; }3.2类模版的实例化类模板实例化与函数模板实例化不同类模板实例化需要在类模板名字后跟​然后将实例化的类型放在中即可类模板名字不是真正的类而实例化的结果才是真正的类。// Stack是类名Stackint才是类型 Stackint st1; // int Stackdouble st2; // double以vector为例#includevectorvectorint这里vector只是提供了一个数据结构具体的储存类型需要通过模版实例化才能推导出来‍同一个命名空间中定义了多个类每个类之前都写了一个模版参数​template class T在每个类中都使用T这样会有冲突吗没有​templateclass T里的 T 是模板参数它的作用域只限于紧跟其后的那个模板声明和它的定义类体/函数体之内。每个类模板各自开启一个独立的作用域彼此的 T 毫无关系在某些时候我们会看到这样使用typename的写法typedef typename rep_type::const_pointer pointer;前面我们提到了可以用class或typename来定义模版参数但这里使用typename明显不是定义模版参数在这里typename的作用是告诉编译器rep_type::const_pointer是一个类型名那为什么这里需要告诉编译器它是个类型名呢其实这句代码不是很完整我也不好把完整代码放过来太多了而且不是很直观。因为rep_type也是一个依赖模版参数的类在编译器编译到上面写出来的这句话之前还没有对rep_type::const_pointer进行实例化为什么不等到模板实例化时再判断因为编译器需要先解析模板的语法在模板参数具体确定之前它无法判断这个名字是类型还是变量。这就导致编译器在编译到上面这句话时会出现歧义模板代码里像rep_type::const_pointer这样的名字可能依赖模板参数。编译器在模板定义阶段还无法确定const_pointer究竟是类型还是其他成员因此用typename明确说明它是类型。typename不会创建新类型也不改变类型只是在模板代码中消除歧义。完