模板详细介绍与应用

张开发
2026/4/11 23:48:25 15 分钟阅读

分享文章

模板详细介绍与应用
一.模板介绍在C中如果我们想要写一个关于int,double的交换函数就需要这样写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; }但是我们发现这除了类型两个函数高度的相似虽然使用函数重载实现了但也有一些坏处1.代码复用率比较低只要有新类型出现时就需要我们自己增加对应的函数。2.代码的可维护性比较低一个出错可能所有的重载均出错。那么能否告诉编译器一个模型让编译器根据不同的类型利用该模型来生成代码呢答案就是模板的出现。泛型编程编写与类型无关的通用代码是代码复用的一种手段。模板是泛型编程的基础。二.函数模板1.概念与格式1.1.概念函数模板代表了一个函数家族该函数模板与类型无关在使用时被参数化根据实参类型产生 函数的特定类型版本1.2.格式templatetypename/class T1, typename/class T2,......,typename/class Tn 返回值类型 函数名(参数列表) { //代码 ... }那么上面的函数就可以写成templateclass T void Swap( T left, T right) { T temp left; left right; right temp; }注意typename是用来定义模板参数关键字也可以使用class(一般都使用这个)。同时C还提供了swap(交换函数)我们以后就无需使用自己创建的交换函数了。但是要包含头文件algorithm1.3.代码演示#includeiostream using namespace std; templateclass T T Add(const T left, const T right) { return left right; } int main() { int a1 10, a2 20; double d1 10.5, d2 70.6; cout Add(a1, a2) endl;; cout Adddouble(d1, d2) endl; return 0; }输出结果从结果可知这个模板是成功的那么编译器是如何识别的呢能够接下来就涉及到模板实例化。2.模板实例化用不同类型的参数使用函数模板时称为函数模板的实例化。模板参数实例化分为隐式实例化和显式实例化。2.1.隐式实例化让编译器根据实参推演模板参数的实际类型当我们写下 模板时编译器并不会直接去调用那个带有 的代码块。它在后台经历了以下三个步骤1.推演编译器查看你传入的参数和类型 。2.实例化编译器确认后 它就会在后台自动生成一份专门给这个使用的函数代码。3.调用程序真正运行的是那个生成的函数。例如以上面的代码为例编译器发现Add(a1, a2)类型为intAdddouble(d1, d2) 为double就会生成// 编译器生成的 int 版 int Add(const int left, const int right) { return left right; } // 编译器生成的 double 版 double Add(const double left, const double right) { return left right; }相当于让编译器为我们干活。2.1.1.注意事项但是如果我们传的是coutAdd(d1, d2)endl;那么该语句不能通过编译因为在编译期间当编译器看到该实例化时需要推演其参数类型 通过实参a1将T推演为int通过实参d1将T推演为double类型但模板参数列表中只有 一个T 编译器无法确定此处到底该将T确定为int 或者 double类型而报错。如图那么我们就需要改变为#includeiostream using namespace std; templateclass T1,class T2 T1 Add(const T1 left, const T2 right) { return left right; } int main() { int a1 10, a2 20; double d1 10.5, d2 70.6; cout Add(a1, a2) endl;; cout Add(a1, d2) endl; return 0; }注意在模板中编译器一般不会进行类型转换操作因为一旦转化出问题编译器就需要 背黑锅。也可以将a1/d1强制类型转换一下例如cout Add((double)a1, d2) endl;但是还有显式实例化这个方法2.2.显式实例化int main(void) { int a 10; double b 20.0; // 显式实例化 Addint(a, b); return 0; }注意如果类型不匹配编译器会尝试进行隐式类型转换如果无法转换成功编译器将会报错3.模板的匹配原则1.一个非模板函数可以和一个同名的函数模板同时存在而且该函数模板还可以被实例化为这 个非模板函数。例如#includeiostream using namespace std; templateclass T1,class T2 T1 Add(const T1 left, const T2 right) { return left right; } // 专门处理int的加法函数 int Add(int left, int right) { return left right; } int main() { int a1 10, a2 20; cout Add(a1, a2) endl;; cout Add(a1, a2) * 10 endl; return 0; }2.对于非模板函数和同名函数模板如果其他条件都相同在调动时会优先调用非模板函数而 不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数 那么将选择模板。例如上面代码的结果为3.模板函数不允许自动类型转换但普通函数可以进行自动类型转换三.类模板1.类模板的定义格式templateclass T1, class T2, ..., class Tn class 类模板名 { // 类内成员定义 };例如templatetypename T class Stack { public: Stack(size_t capacity 4) { _array new T[capacity]; _capacity capacity; _size 0; } private: T* _array; size_t _capacity; size_t _size; };注意模版不建议声明和定义分离到两个文件.h 和.cpp会出现链接错误具体原因后面会讲。2.类模板实例化类模板实例化与函数模板实例化不同类模板实例化需要在类模板名字后跟然后将实例化的 类型放在中即可类模板名字不是真正的类而实例化的结果才是真正的类。例如#includeiostream using namespace std; // 类模版 templateclass 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; }四.非类型模板参数1.概念模板参数分类类型形参与非类型形参。类型形参出现在模板参数列表中跟在class或者typename之类的参数类型名称。非类型模板参数就是用一个常量作为类(函数)模板的一个参数在类(函数)模板中可将该参数当成常量来使用。例如#includeiostream using namespace std; templateclass T, size_t N 10 class arr_ay { public: T operator[](size_t index) { return _array[index]; } const T operator[](size_t index)const { return _array[index]; } size_t size()const { return _size; } bool empty()const { return 0 _size; } private: T _array[N]; size_t _size; }; int main() { arr_ayint, 10 arr1; arr_ayint, 5 arr2; return 0; }如代码中的size_t N 10通过改变N我们就可以实现不同大小的数组。如果我们想要使用缺省参数建议这样写arr_ay.注意1. 浮点数和类对象是不允许作为非类型模板参数的。2. 非类型的模板参数必须在编译期就能确认结果。2.array类在C中有array这么个静态数组如图从中可以看出array就是非类型模板的一个经典例子。具体内容可以看array - C 参考五.模板的特化概念通常情况下使用模板可以实现一些与类型无关的代码但对于一些特殊类型的可能会得到一些 错误的结果需要特殊处理。1.函数模板特化假设我们有一个通用的模板Less用来比较两个值的大小templateclass T bool Less(T left, T right) { return left right; }如果传int/double 均无问题但是如果传的是指针比如字符串那么比较的是内存地址而不是字符串的内容这就出错了要解决上面的问题我们需要对函数进行特化。1.1.特点1. 必须要先有一个基础的函数模板2. 关键字template后面接一对空的尖括号3. 函数名后跟一对尖括号尖括号中指定需要特化的类型4. 函数形参表: 必须要和模板函数的基础参数类型完全相同如果不同编译器可能会报一些奇 怪的错误。例如// 1. 基础模板 templateclass T bool Less(T left, T right) { return left right; } // 2. 对 const char* 类型进行特化 template bool Lessconst char*(const char* left,const char* right) { return strcmp(left, right) 0; // 改用字符串比较逻辑 }2.缺点但是在 C 中函数特化其实并不常用与其写特化还不如写一个普通的函数编译器会优先调用它效果和特化一模一样。例如bool Less(const char* left,const char* right) { return strcmp(left, right) 0; }该种实现简单明了代码的可读性高容易书写对于一些参数类型复杂的函数模板特化 时特别给出因此函数模板不建议特化。2.类模板特化2.1.全特化将模板参数列表中所有的参数都确定化例如#includeiostream #includearray using namespace std; templateclass T1, class T2 class Data { public: Data() { cout DataT1, T2 endl; } private: T1 _d1; T2 _d2; }; template class Dataint, char { public: Data() { cout Dataint, char endl; } private: int _d1; char _d2; }; void TestVector() { Dataint, int d1; Dataint, char d2; } int main() { TestVector(); return 0; }输出结果2.2.偏特化例如#includeiostream #includearray using namespace std; templateclass T1, class T2 class Data { public: Data() { cout DataT1, T2 endl; } private: T1 _d1; T2 _d2; }; //偏特化 template class T1 class DataT1, int { public: Data() { cout DataT1, int endl; } private: T1 _d1; int _d2; }; void TestVector() { Dataint, int d1; Dataint, char d2; } int main() { TestVector(); return 0; }输出结果五.模板分离编译1.概念一个程序项目由若干个源文件共同实现而每个源文件单独编译生成目标文件最后将所有 目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。假设模板可以分离编译例如// a.h templateclass T T Add(const T left, const T right);//声明// a.cpp templateclass T T Add(const T left, const T right) { return left right;//定义 }// main.cpp #includeiostream #includea.h int main() { Add(1, 2); Add(1.0, 2.0); return 0 }分析总之当编译时由于a.cpp中的函数是一个没有实例化的模板编译器无法将将它编译成机器语言这也就导致.c中的Add无法在链接时与其对应就导致了编译报错。2.解决方法1.将声明和定义放到一个文件 xxx.hpp 里面或者xxx.h其实也是可以的。推荐使用这种。2. 模板定义的位置显式实例化。这种方法不实用不推荐使用。模板总结 【优点】1. 模板复用了代码节省资源更快的迭代开发C的标准模板库(STL)因此而产生2. 增强了代码的灵活性【缺陷】 1. 模板会导致代码膨胀问题也会导致编译时间变长2. 出现模板编译错误时错误信息非常凌乱不易定位错

更多文章