
目录一、泛型编程二、模板2.1 模板的定义2.2 模板的分类2.3 函数模板格式2.4 函数模板的原理2.5 函数模板的实例化2.5.1 隐式实例化2.5.2 显示实例化2.6 模板参数的匹配原则三、类模板3.1 类模板——栈3.2 类模板的实例化3.3 模板注意事项引入如何实现一个通用的交换函数呢使用函数重载多个比较函数吗void Swap(int left, int right) { int temp left; left right; right temp; } 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; } …………使用函数重载虽然可以实现但是有几个不好的地方重载的函数仅在类型上存在差异逻辑部分完全重复。每当出现新的数据结构就得添加对应的重载版本由于大量重复代码的存在一旦需求变更或发现错误所有重载版本都需要同步修改极易引入遗漏或新错误。那能否告诉编译器一个模子让编译器根据不同的类型利用该模子来生成代码呢如果在C中也能够存在这样一个模具通过给这个模具中填充不同材料(类型)来获得不同材料的铸件(即生成具体类型的代码那将会节省许多头发。巧的是前人早已将树栽好我们只需在此乘凉。一、泛型编程泛型编程是一种编程范式其核心思想是编写与具体数据类型无关的通用代码从而实现代码的高度复用。在 C 中泛型编程主要通过模板(template)来实现。简单来说泛型编程是编写与类型无关的通用代码是代码复用的一种手段。模板是泛型编程的基础。二、模板2.1 模板的定义C模板是泛型编程的核心工具其核心思想是类型参数化即将数据类型作为参数传递使代码逻辑与具体数据类型解耦实现 “一次编写多处复用”。2.2 模板的分类模板主要分为函数模板和类模板两种形式函数模板用于定义可处理任意类型数据的通用函数编译器会根据调用时的实参类型自动生成对应类型的具体函数。类模板用于定义通用的类结构其成员变量、成员函数的类型可由模板参数指定使用时需显式或隐式指定类型参数以实例化具体的类。2.3 函数模板格式templatetypename T1, typename T2, ..., typename Tn 返回值类型 函数名(参数列表){}回到刚刚的问题-----实现一个通用的交换函数templatetypename T void Swap( T left, T right) { T temp left; left right; right temp; }一个函数模板就能实现int、double、char等类型的swap函数使用注意typename是用来定义模板参数关键字也可以使用class(切记不能使用struct代替class)2.4 函数模板的原理函数模板是一个蓝图它本身并不是函数是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器。在编译器编译阶段对于模板函数的使用编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如当用double类型使用函数模板时编译器通过对实参类型的推演将T确定为double类型然后产生一份专门处理double类型的代码对于字符类型也是如此。注意如果同一个函数模板与相关函数同时存在编译器会优先调用现成的函数。2.5 函数模板的实例化用不同类型的参数使用函数模板时称为函数模板的实例化。模板参数实例化分为隐式实例化和显式实例化。2.5.1 隐式实例化隐式实例化--------》让编译器根据实参推演模板参数的实际类型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(a, (int)d); return 0; }注意下面实例化是不行的int main() { int a1 10; double d1 10.0; Add(a1,d1); return 0; }因为该语句不能通过编译因为在编译期间当编译器看到该实例化时需要推演其实参类型通过实参a1将T推演为int通过实参d1将T推演为double类型但模板参数列表中只有一个T编译器无法确定此处到底该将T确定为int或者double类型而报错。注意在模板中编译器一般不会进行类型转换操作因为一旦转化出问题编译器就需要背黑锅如果想要使用可以用户自己来强制转化使用显式实例化Add(a, (int)d);2.5.2 显示实例化显示实例化-------》在函数名后的中指定模板参数的实际类型int main(void) { int a 10; double b 20.0; // 显式实例化 Addint(a, b); return 0; }如果类型不匹配编译器会尝试进行隐式类型转换如果无法转换成功编译器将会报错。2.6 模板参数的匹配原则一个非模板函数可以和一个同名的函数模板同时存在而且该函数模板还可以被实例化为这个非模板函数// 专门处理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函数 }注 模板函数不允许自动类型转换但普通函数可以进行自动类型转换三、类模板templateclass T1, class T2, ..., class Tn class 类模板名 { // 类内成员定义 };3.1 类模板——栈// 类模版 templatetypename T class stack { public: Stack(size_t capacity 4) { _str new T[capacity]; _capacity capacity; _size 0; } ~stack() { delete[]_str; _str nullptr; _size 0; _capacity 0; } void stack_pushback(T x) { if (_size _capacity) { T* tmp new T[_capacity*2]; memcpy(tmp,_str,sizeof(T)*_size); delete[](_str); _str tmp; _capacity * 2; } _str[_size] x; } private: T* _str; size_t _capacity; size_t _size; };3.2 类模板的实例化类模板实例化与函数模板实例化不同类模板实例化需要在类模板名字后跟然后将实例化的类型放在中即可类模板名字不是真正的类而实例化的结果才是真正的类。// Stack是类名Stackint才是类型 stackint st1; // int stackdouble st2; // double3.3 模板注意事项每一个模板只能给当前的函数或者是类使用如果超出这个范畴就不能使用所以在成员函数的声明定义分离的时候我们采用重新定义模板类域模板参数的方式注意模版不建议声明和定义分离到两个文件.h 和.cpp会出现链接错误