【C++】模板初阶: 解析模板原理、实例化与特化 相关专栏【Linux专栏】【C语言专栏】【测试专栏】【MySQL专栏】【C 专栏】 相关文章推荐【C】STL从零掌握STL容器特性与实战用法【C】C类与对象2C构造函数、运算符重载与流输入输出全面解析【测试】一文吃透软件测试全分类入门必懂核心体系【Linux】一文搞懂HTTP协议概念、报文格式与极简服务器实现很高兴你点开这篇文章✨这里会持续更新我喜欢的内容关注我一起慢慢变好呀 点赞 ⭐ 收藏 评论文章目录前言一、函数模板1.1 为什么需要函数模板1.2 函数模板的语法1.3 模板的使用二、模板参数推导与实例化2.1 隐式实例化2.2 参数不匹配时的处理2.3 多类型模板参数2.4 显式实例化三、模板重载与匹配规则3.1 函数模板与普通函数可以重载3.2 匹配优先级四、类模板4.1 类模板的定义4.2 类模板成员函数的类外定义4.3 类模板的使用显式实例化五、模板的注意事项5.1 声明与定义分离的问题5.2 模板的编译原理5.3 模板的缺点六、完整示例通用Stack类七、知识点汇总八、常见面试题前言在C语言中如果我们要写一个交换两个整数的函数再写一个交换两个浮点数的函数我们只能写两个不同名的函数或者用宏但宏有很多坑。C提供了模板来解决这个问题。有了模板我们就可以写出类型相关的通用代码。这一篇我们来学习函数模板如何写一个通用的Swap函数模板参数推导编译器如何自动推断类型显式实例化强制指定模板参数类型类模板如何写一个通用的Stack容器 ✨ 一、函数模板1.1 为什么需要函数模板看看这个例子我们需要交换两个变量的值但int、double、char都需要写一个版本。// 代码重复严重voidSwap(intleft,intright){inttempleft;leftright;righttemp;}voidSwap(doubleleft,doubleright){doubletempleft;leftright;righttemp;}voidSwap(charleft,charright){chartempleft;leftright;righttemp;}函数模板// 一个模板搞定所有类型templatetypenameTvoidSwap(Tleft,Tright){T templeft;leftright;righttemp;}1.2 函数模板的语法// template 关键字 typename T 或 class TtemplatetypenameTvoidSwap(Tx,Ty){T tmpx;xy;ytmp;}// 多个模板参数templatetypenameT1,typenameT2voidfunc(constT1x,constT2y){// ...}注意typename和class在模板参数中完全等价没有区别。templatetypenameT// 推荐更语义化templateclassT// 也可以C早期用法1.3 模板的使用intmain(){inti1,j2;doublem1.1,n2.2;Swap(i,j);// 编译器推导 T intSwap(m,n);// 编译器推导 T double// Swap(i, n); // 错误T被推导成int还是double矛盾return0;} ✨ 二、模板参数推导与实例化2.1 隐式实例化编译器会根据你传入的实参类型自动推导模板参数templatetypenameTTAdd(constTleft,constTright){returnleftright;}intmain(){inta110,a220;doubled110.1,d220.2;Add(a1,a2);// 隐式实例化T → intAdd(d1,d2);// 隐式实例化T → doublereturn0;}2.2 参数不匹配时的处理编译器根据你传入的实参类型自动推导模板参数Add(a1,d1);// 错误T被推导成int还是double解决方法1强制类型转换coutAdd(a1,(int)d1)endl;// 都转成intcoutAdd((double)a1,d1)endl;// 都转成double解决方法2显式实例化推荐coutAddint(a1,d1)endl;// 明确指定 T intcoutAdddouble(a1,d1)endl;// 明确指定 T double2.3 多类型模板参数templatetypenameT1,typenameT2T1Add(constT1left,constT2right){returnleftright;}intmain(){inta110;doubled120.2;coutAdd(a1,d1)endl;// T1int, T2double返回intreturn0;}2.4 显式实例化templatetypenameTT*func1(intn){returnnewT[n];}intmain(){// 无法推导T必须显式指定int*p1func1int(10);// T → intdouble*p2func1double(10);// T → doubledelete[]p1;delete[]p2;return0;} ✨ 三、模板重载与匹配规则3.1 函数模板与普通函数可以重载// 函数模板templatetypenameTTAdd(constTleft,constTright){couttemplate Add: ;returnleftright;}// 普通函数特化版本intAdd(constintx,constinty){coutnormal Add: ;return(xy)*10;}intmain(){inta110,a220;coutAdd(a1,a2)endl;// 输出normal Add: 300// 普通函数优先级更高coutAddint(a1,a2)endl;// 输出template Add: 30// 显式指定强制调用模板doubled11.1,d22.2;coutAdd(d1,d2)endl;// 输出template Add: 3.3// 没有匹配的普通函数调用模板return0;}3.2 匹配优先级优先级匹配规则1最高完全匹配的普通函数2通过模板实例化得到匹配函数3最低通过类型转换匹配 ✨ 四、类模板4.1 类模板的定义templatetypenameTclassStack{public:Stack(intn4):_array(newT[n]),_size(0),_capacity(n){}~Stack(){delete[]_array;_arraynullptr;_size_capacity0;}voidPush(constTx);private:T*_array;size_t _capacity;size_t _size;};4.2 类模板成员函数的类外定义关键类外定义时需要加上templatetypename T并用类名T::指定作用域。// 类外定义成员函数templatetypenameTvoidStackT::Push(constTx){if(_size_capacity){// 扩容逻辑T*tmpnewT[_capacity*2];memcpy(tmp,_array,sizeof(T)*_size);delete[]_array;_arraytmp;_capacity*2;}_array[_size]x;}4.3 类模板的使用显式实例化注意类模板不支持隐式实例化必须显式指定模板参数类型。intmain(){// 显式实例化指定T为intStackintst1;st1.Push(1);st1.Push(2);st1.Push(3);// 显式实例化指定T为doubleStackdoublest2;st2.Push(1.1);st2.Push(2.2);st2.Push(3.3);// 动态分配类模板对象Stackdouble*pstnewStackdouble;pst-Push(10.5);deletepst;return0;} ✨ 五、模板的注意事项5.1 声明与定义分离的问题模板的声明和定义通常不能分离到.h和.cpp文件中。// Stack.htemplatetypenameTclassStack{public:voidPush(constTx);};// Stack.cpp 错误链接时会找不到定义templatetypenameTvoidStackT::Push(constTx){/* ... */}解决办法将定义直接写在.h文件中或者在.cpp文件末尾显式实例化需要的类型// Stack.cpp - 显式实例化templateclassStackint;templateclassStackdouble;5.2 模板的编译原理模板在编译阶段根据使用情况生成具体代码编译器看到模板定义时不会生成代码编译器看到实例化如Stackint时才会生成对应的类代码不同的实例化生成不同的类Stackint和Stackdouble是不同类型5.3 模板的缺点缺点说明编译慢每次实例化都要重新生成代码代码膨胀不同类型生成多份代码错误信息复杂模板编译错误信息难以阅读声明定义难分离通常只能写在头文件 ✨ 六、完整示例通用Stack类#includeiostream#includestringusingnamespacestd;templatetypenameTclassStack{public:Stack(intn4):_array(newT[n]),_size(0),_capacity(n){coutStack()endl;}~Stack(){delete[]_array;_arraynullptr;_size_capacity0;cout~Stack()endl;}voidPush(constTx){if(_size_capacity){Expand();}_array[_size]x;}voidPop(){if(_size0)_size--;}TTop(){return_array[_size-1];}boolEmpty()const{return_size0;}size_tSize()const{return_size;}private:voidExpand(){T*tmpnewT[_capacity*2];for(size_t i0;i_size;i){tmp[i]_array[i];}delete[]_array;_arraytmp;_capacity*2;}T*_array;size_t _capacity;size_t _size;};intmain(){// int栈StackintintStack;intStack.Push(10);intStack.Push(20);intStack.Push(30);while(!intStack.Empty()){coutintStack.Top() ;intStack.Pop();}coutendl;// 30 20 10// string栈StackstringstrStack;strStack.Push(hello);strStack.Push(world);coutstrStack.Top()endl;// worldreturn0;} ✨ 七、知识点汇总知识点核心要点函数模板template 函数定义模板参数typename和class完全等价隐式实例化编译器自动推导参数类型显式实例化FuncName(a, b)强制指定类模板必须显式实例化如Stack类外定义需template Stack::匹配优先级普通函数 模板实例化 类型转换编译特性模板在实例化时才生成代码 ✨ 八、常见面试题Q1typename和class在模板中有什么区别在模板参数中完全等价。但typename还可以用于嵌套依赖类型例如typename T::iterator。Q2函数模板可以隐式实例化类模板为什么不行函数模板编译器可以实参推导类模板没有推导依据构造函数实参可以推导C17开始支持。C17开始类模板也支持部分隐式推导CTAD。Q3模板声明和定义为什么不能分离模板在实例化时才生成代码。如果定义在.cpp文件其他文件包含.h时看不到定义无法实例化导致链接错误。Q4模板代码膨胀怎么解决将不依赖模板参数的公共代码抽取到基类或单独的函数中。下一篇我们来学习STL初识vector、list、map等迭代器的使用 ✨ 谢谢你看到这里呀如果喜欢这篇内容点个关注下次更新不迷路✨ 点赞 ⭐ 收藏 评论